0

Using Xcode Version 15.0 beta (15A5160n) steps to reproduce:

  • Insert the code below into a new project
  • Build and run
  • First time, tap the “Setup” button to populate the SwiftData container.
  • All of the Attribute objects will be shown together sorted by
    ‘Attribute.order’
  • Comment out the @Query with the single SortDescriptor and then uncomment the @Query with two SortDescriptor's.
  • Run the project again and it will crash with the following console message:

SwiftData/DataUtilities.swift:1179: Fatal error: Unexpected type for Expansion: Item

The expectation was for the Attributes to be listed in order within groups for each Item object, also in order by their Item.order property:

Item[0] Attribute[0]
Item[0] Attribute[1]
Item[0] Attribute[2]
Item[1] Attribute[0]
Item[1] Attribute[1]
Item[1] Attribute[2]
Item[2] Attribute[0]
Item[2] Attribute[1]
Item[2] Attribute[2]

I have filed a Feedback for this crash, which occurs when running this project on both the macOS Sonoma beta and the iOS 17.0 iPhone 14 Pro simulator.

Should this work? Is there a workaround?

**** BTW, this same sorting pattern has worked well with Core Data in the past.

import SwiftUI
import SwiftData

@Model
final class Item {
    
    var name: String
    var order: Int
    @Relationship(.cascade) var attributes: [Attribute] = []
    
    init(name: String, order: Int) {
        self.name = name
        self.order = order
    }
}

@Model
final class Attribute {
    
    var name: String
    var order: Int
    @Relationship var item: Item

    init(item: Item, name: String, order: Int) {
        self.item = item
        self.name = name
        self.order = order
    }
}

struct ContentView: View {
    
    @Environment(\.modelContext) private var modelContext
    
    @Query(FetchDescriptor<Attribute>(sortBy: [SortDescriptor(\Attribute.order, order: .forward)]))
    //    @Query(FetchDescriptor<Attribute>(sortBy: [SortDescriptor(\Attribute.item.order, order: .forward),
    //                                               SortDescriptor(\Attribute.order, order: .forward)]))
    private var attributes: [Attribute]
    
    var body: some View {
        NavigationView {
            List {
                ForEach(self.attributes) { attribute in
                    Text("Item[\(attribute.item.order)] Attribute[\(attribute.order)]")
                        .monospaced()
                }
            }
            .toolbar {
                ToolbarItem {
                    Button("Setup", action: self.setup)
                }
            }
        }
    }

    private func setup() {
        withAnimation {
            do {
                
                let attributeDescriptor = FetchDescriptor<Attribute>()
                let attributes = try self.modelContext.fetch(attributeDescriptor)
                for attribute in attributes {
                    self.modelContext.delete(attribute)
                }
                
                let itemDescriptor = FetchDescriptor<Item>()
                let items = try self.modelContext.fetch(itemDescriptor)
                for item in items {
                    self.modelContext.delete(item)
                }
                try self.modelContext.save()

                let item1 = Item(name: "Z", order: 0)
                self.modelContext.insert(Attribute(item: item1, name: "\(item1.name).0", order: 0))
                self.modelContext.insert(Attribute(item: item1, name: "\(item1.name).1", order: 1))
                self.modelContext.insert(Attribute(item: item1, name: "\(item1.name).2", order: 2))
                self.modelContext.insert(item1)

                let item2 = Item(name: "Y", order: 1)
                self.modelContext.insert(Attribute(item: item2, name: "\(item2.name).0", order: 0))
                self.modelContext.insert(Attribute(item: item2, name: "\(item2.name).1", order: 1))
                self.modelContext.insert(Attribute(item: item2, name: "\(item2.name).2", order: 2))
                self.modelContext.insert(item2)

                let item3 = Item(name: "X", order: 2)
                self.modelContext.insert(Attribute(item: item3, name: "\(item3.name).0", order: 0))
                self.modelContext.insert(Attribute(item: item3, name: "\(item3.name).1", order: 1))
                self.modelContext.insert(Attribute(item: item3, name: "\(item3.name).2", order: 2))
                self.modelContext.insert(item2)

                try self.modelContext.save()
            } catch {
                let nsError = error as NSError
                fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
            }
        }
    }

}
HangarRash
  • 7,314
  • 5
  • 5
  • 32
Chuck H
  • 7,434
  • 4
  • 31
  • 34
  • I think you have done it correctly, and this is a SwiftData bug. [Apple's Documentation](https://developer.apple.com/documentation/swiftdata/query/init(filter:sort:transaction:)) is pretty clear that `sortDescriptors` is plural. – Yrb Jun 18 '23 at 15:28
  • I've had very little success filing Feedback's in the past. This Feedback is FB12346766 in case anyone else want to pile on. – Chuck H Jun 19 '23 at 16:28
  • I actually got a response to one of mine in January... – Yrb Jun 19 '23 at 17:18
  • Is a inverse relationship not working ? @Relationship(.cascade, inverse: \Attribute.item) – Marc T. Jun 21 '23 at 05:51
  • This seems to work now in Xcode 15 beta 2 – Joakim Danielson Jun 22 '23 at 09:23
  • Yes, I can also confirm that this issue was fixed in beta 2 and continues to work in beta 3. – Chuck H Jul 06 '23 at 19:31

1 Answers1

-1

I think it won't work, attribute is the name given to a property of an entity so shouldn't be the name of an entity. Item and Attribute are essentially the same entity with one attribute name so just have one entity with a relation to itself.

malhal
  • 26,330
  • 7
  • 115
  • 133
  • Item and Attribute are NOT the same entity. Aside from the Relationships, they each just happen to have exactly the same stored properties for this simplified example. As I said in my question, this same pattern works well when using Core Data. – Chuck H Jun 19 '23 at 16:25