Home > Enterprise >  Hierarchical List in SwiftUI with CoreData
Hierarchical List in SwiftUI with CoreData

Time:10-30

I am trying to implement hierarchical CoreData in a SwiftUI List. I created a new project in Xcode using CoreData and just added a parent(to-one)/children(t-many) relationship to the Item entity: enter image description here

I use Codegen Manual/None for Item and created NSManagedObject subclasses for Item. I added an extension to return the children as an Array (so that I can use it in List for the children: parameter):

public var childrenArray: [Item]? {
    let set = children as? Set<Item> ?? []
    print(set)
    return set.sorted(by: { $0.id?.uuidString ?? "NO ID?" < $1.id?.uuidString ?? "NO ID?" })
}

for Item.

In the ContentView.swift, I changed it to use a List with the children: parameter to show my hierarchical data. I also changed the @FetchRequest so that only Items without a parent are obtained:

import SwiftUI
import CoreData

struct ContentView: View {
@Environment(\.managedObjectContext) private var viewContext

@FetchRequest(
    sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)], predicate: NSPredicate(format: "parent == nil"), animation: .default) private var items: FetchedResults<Item>

var body: some View {
    NavigationView {
        List(Array(items), children: \.childrenArray) { item in
            NavigationLink {
                VStack {
                    Text(item.id!.uuidString)
                }
            } label: {
                Text(item.id!.uuidString)
            }
        }
        .toolbar {
            ToolbarItem {
                Button(action: addItem) {
                    Label("Add Item", systemImage: "plus")
                }
            }
        }
        Text("Select an item")
    }
}

private func addItem() {
    withAnimation {
        let newItem = Item(context: viewContext)
        newItem.timestamp = Date()
        newItem.id = UUID()
        
        // Add a new item and make it a children of first new item
        let newItem2 = Item(context: viewContext)
        newItem2.timestamp = Date()
        newItem2.id = UUID()
        
        newItem.addToChildren(newItem2)
        // ------------------------------------------------------

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

The result works, but I get a down-chevron for the item that does not have children: enter image description here

Reading Apples doc about List I understand that the children array should be nil to tell the List to not show a chevron. But I can't figure out how to do that in the childrenArray function. How would I have to adopt the childrenArray function to get the right array so that the List will not show a chevron?

CodePudding user response:

Simply make the computed property return nil by using a guard statement and return nil if there are no children

 guard let set = children as? Set<Item>, set.isEmpty == false else { return nil }

Full code

extension Item {
    public var childrenArray: [Item]? {
        guard let set = children as? Set<Item>, set.isEmpty == false else { return nil }
        return set.sorted(by: { $0.id?.uuidString ?? "NO ID?" < $1.id?.uuidString ?? "NO ID?" })
    }
}
  • Related