Clifton Labrum
Clifton Labrum

Reputation: 14060

Accessing Core Data Child Objects in Nested SwiftUI List

My end goal is to create a SwiftUI List with children. I have sections (parent) that contain projects (children), and the sections will expand and collapse their list of projects.

I have this built with an NSOutlineView using Realm, but now I'm trying to build it with SwiftUI and Core Data. *GULP* : )

enter image description here

I'm not very experienced with Core Data, but I think I have my data set up right. I have a Section entity and a Project entity, and the Section has a projects attribute that has a to-many relationship.

enter image description here

So, I presume (incorrectly as shown below) section.projects is a collection of Project objects.

For simplicity, I'm just trying to nest to the two inside a List (I'll worry about the DisclosureGroup stuff later):

List{
  ForEach(sections, id: \.recordName){ section in
    Text(section.name)
    ForEach(section.projects, id: \.recordName){ project in //<-- ERROR
      Text(project.name)
    }
  }
}

The line marked above has two errors that seems to suggest I don't have an array of objects to work with in section.projects:

Referencing initializer 'init(_:id:content:)' on 'ForEach' requires that 'NSObject' conform to 'RandomAccessCollection'

Value of optional type 'NSOrderedSet?' must be unwrapped to a value of type 'NSOrderedSet'

If section.projects is an NSOrderedSet, why can't I iterate over it?

Upvotes: 1

Views: 1822

Answers (1)

Clifton Labrum
Clifton Labrum

Reputation: 14060

I figured this out with the help of this article: https://www.hackingwithswift.com/books/ios-swiftui/one-to-many-relationships-with-core-data-swiftui-and-fetchrequest

I had to change my Core Data entities to have their Codegen setting be Manual/None (in the .xcdatamodeld editor in Xcode). I then select my entities and go to Editor > Create NSManagedObject Subclass... which created a bunch of files that reflect my Core Data models.

Then inside the Section+CoreDataProperties.swift file, I added a computed property to give me access to my projects (sorted by name):

public var projectArray: [Project] {
  let set = projects as? Set<Project> ?? []
  return set.sorted {
    $0.wrappedName < $1.wrappedName
  }
}

And then, for convenience, inside Project+CoreDataProperties.swift I added this:

public var wrappedName: String{
  name ?? "Unknown Name"
}

...which allows my computed property to sort my projects by name (since Core Data treats strings as optionals by default).

As a result, I was able to iterate over my nested data like this:

ForEach(sections, id: \.self) { section in
  Text(section.wrappedName)
        
  ForEach(section.projectArray, id: \.self) { project in
    Text(project.wrappedName)
  }
}

Upvotes: 3

Related Questions