1

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?

1 Answer 1

3

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)
  }
}
Sign up to request clarification or add additional context in comments.

1 Comment

Clifton worth mentioning perhaps and as an aside, for simplicity you might like to use Codegen = Class Definitionand instead of manually preparing your NSManagedObject subclasses, write an extension for each Core Data entity that you require computed properties for (e.g. extension Project { ... } for your wrappedName computed var).

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.