listswiftuichildrenswift-data

How to create a List with children tree from SwiftData model


I'm trying to store this model using SwiftData in order to display it within List with children tree.

However List requires its children to be of the same type as the parent and that's an issue for SwiftData as it seems. Immediately after I build and run following code I get an "Unknown related type error" fatal within the model macro.

Model:

@Model final class LibraryItem: Identifiable {
    @Attribute(.unique) let id: UUID
    var name: String
    ...
    var subMenuItems: [LibraryItem]?
}

Error:

enter image description here

I build the tree using a few recursive functions to reflect the file system hierarchy of given URL. This is my List:

List(
    libraryItems,
    children: \.subMenuItems,
    selection: Binding(
        get: {
            selectedLibraryItemId
        },
        set: { ids, _ in
            selectionHandler(ids)
        }
    )
) { item in
    Text(item.name)
}

And this is how I initialise the ModelContainer:

init() {
    let schema = Schema([LibraryItem.self])
    let config = ModelConfiguration(schema: schema, groupContainer: .identifier("antonincharvat.com.appName"))
        
    do {
        let container = try ModelContainer(for: schema, configurations: config)
        self.libraryItemsModelContainer = container
    } catch {
        fatalError("Couldn't create ModelContainer")
    }
}

Is there any way for this to work? I tried to use computed property as the children collection, but List initialises all its children at once causing this approach unusable.


Solution

  • As I see it there are two things you need to change in your model to make this work, create a second property for the inverse of the relationship and also use the @Relationship wrapper for one of the relationship properties to exclusively point out what the key path is to the opposite property of the relationship.

    This is what my LibraryItem model ended up like

    @Model final class LibraryItem: Identifiable {
        @Attribute(.unique) let id: UUID
        var name: String
        var subMenuItems: [LibraryItem]?
    
        @Relationship(deleteRule: .nullify, inverse: \LibraryItem.subMenuItems)
        var parentItem: LibraryItem?
    
        init(name: String) {
            self.id = UUID()
            self.name = name
        }
    }