List Core Data Entity Relationships

How do I list the relationships of a particular entity that is fetched?


I know how to list the entries for the entity itself, but I can't figure out how to list one-to-many relationships in a List.


See this code:


struct TodoItemView: View {
    var todoList: TodoList

    var body: some View {
        VStack {
            Text(todoList.title!)

            List {
                ForEach(todoList.todoItems, id: \.self) {todoItem in
                    Text(todoItem.title!)
                }
            }
        }
    }
}


That is producing errors and I have no clue how to fix them:


  • Protocol type 'NSSet.Element' (aka 'Any') cannot conform to 'Hashable' because only concrete types can conform to protocols
  • Value of type 'NSSet.Element' (aka 'Any') has no member 'title'


I've set up my TodoList entity to have a one-to-many with TodoITem. I fetch the todoList in the previous view and then pass it to this view:


@Environment(\.managedObjectContext) var managedObjectContext
@FetchRequest(
    entity: TodoList.entity(),
    sortDescriptors: [NSSortDescriptor(key: "order", ascending: true)]
) var todoLists: FetchedResults<TodoList>

// ...more code here

List {
    ForEach(todoLists, id: \.self) {todoList in
        NavigationLink(destination: TodoItemView(todoList: todoList), label: {
            Text(todoList.title!).foregroundColor(stringToColor(string: todoList.color!))
        })
    }
}


How do I use todoList.todoItems in a ForEach loop the same way so that I can also run CRUD operations on it as well?

Replies

Can you share the code for the TodoList type? It sounds as though the `todoItems` property is defined only as an NSSet, not a set of some particular type. If you cast it to the appropriate concrete type (i.e. Set<TodoItem>) then you should find that the errors go away.

I'm not using manual classes for the entities....so I need to create my own entity class and cast the relationship?

I'm having the same issue. Have you figured this out yet?

hi,


the ToDoList and the ToDoItem relationship is one-to-many, so (if you take the usual, default Class Definition codegen method), XCode will generate the ToDoList class having an property toDoItems: NSSet?.


you can turn those items into an array of the right type and work with it using something like this:


struct TodoItemView: View { 
  var todoList: TodoList 
  var toDoItems: [ToDoItem] = {
    if let items = todoList.toDoItems as? Set {
      return Array(items)
    }
    return [ToDoItem]()
  }

  var body: some View { 
    VStack { 
      Text(todoList.title!) 
      List { 
        ForEach(toDoItems, id: \.self) {todoItem in 
          Text(todoItem.title!) 
        } 
      } 
    } 
  } 
}



i think this works -- but, apologies, i haven't tested (i'm guessing a little on this, but i have used a type conversion like the one above several times [thanks, Paul Hudson @twostraws]).


hope this helps,

DMG

Hi @DelawareMathGuy I solved this in my code exactly like in your solution, but I find that when changing a variable like a boolean "done" and saving the viewContext the value is stored, but the SwiftUI View doesn't update. How could I fix that?
hi,

in response to @toqix, i don't know exactly the definition of your entities, but the general principle is that if entity A has a one-to-many relationship with entity B (that is, each A can have many Bs, but each B is associated with only one A), then changing an attribute's value on a B does not trigger a SwiftUI view update on a view that is driven by a @FetchRequest or @ObservedObject for A -- because no attribute of A has been changed.

the solution is usually that if you change an attribute's value on a B, then also determine its associated A, possibly some a = B.referenceBackToA (depending on how you named the relationship in B), and call a?.objectWillChange.send().

hope that helps,
DMG