How to tie NSFetchedResultsController to List

I've got a simple data model that includes a PassthroughSubject like so:


final class PeopleDataModel: NSObject, ObservableObject {
    var didChange = PassthroughSubject<Void, Never>()

    private lazy var fetchedResultsController: NSFetchedResultsController<Person> = {
        ...
    }()

    public var people: [Person] {
        return fetchedResultsController.fetchedObjects ?? []
    }
}

extension PeopleDataModel: NSFetchedResultsControllerDelegate {
    func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
        people.forEach { print($0) }
        didChange.send()
    }
}


So my intent is when Core Data is updated, it'll send the message out. Now I try and use it in my SwiftUI view:


struct ContentView : View {
    @ObservedObject var peopleDataModel = PeopleDataModel()

    var body: some View {
        NavigationView {
            List(peopleDataModel.people) { person in
                NavigationLink(destination: PersonDetailView(person: person)) {
                    PersonRow(person: person)
                }
            }
            .navigationBarTitle(Text("People"))
        }
    }
}


The view doesn't actually update with the people that have been inserted into Core Data. They show properly in the print from the data model, but don't appear in the List. How do I actually "tie" these things together so that when Core Data updates the List knows that it needs to redraw itself?

Replies

Code Block
class PeopleDataModel: NSObject, ObservableObject, NSFetchedResultsControllerDelegate {
private lazy var fetchedResultsController: NSFetchedResultsController<Person> = {
        let frc = NSFetchedResultsController<Person>(fetchRequest: Person.sortedFetchRequest(), managedObjectContext: PersistenceController.shared.container.viewContext, sectionNameKeyPath: nil, cacheName: nil)
        frc.delegate = self
try! fetchedResultsController.performFetch()
        return frc
}()
public var people: [Person] {
return fetchedResultsController.fetchedObjects ?? []
}
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
objectWillChange.send()
}
}
struct ContentView : View {
@StateObject var peopleDataModel = PeopleDataModel()


Or instead of the people getter we could set the fetchedObjects to a @Published property, I'm not sure which is best yet.

If you need to change the fetchRequest, call a fetch(...) method from onAppear, and set the predicate or new sort descriptor and call performFetch there instead. e.g.

Code Block
    func fetch(name:String){
        objectWillChange.send()
        fetchedResultsController.fetchRequest.predicate = NSPredicate(format: "name = %@", name)
        try! fetchedResultsController.performFetch()
    }


Code Block
        .onAppear() {
            peopleDataModel.fetch(name: name)
        }