Post

Replies

Boosts

Views

Activity

SwiftUI Core Data Relationship Not Updating View
I've run into a problem with the following setup: I have a Core Data database with parent objects with a relationship to many child objects. I have a main view using SwiftUI FetchedResults that displays the parent objects in a list and properties about the child objects. I have a second view that lets you change properties of the child objects. The parent object has the property: name (string). The child object has: name (string) and star (bool). On the main view I display a list of parent objects and how many child objects are starred. On the second view I update if a child object is starred. Upon updating the property on the second view and saving, the first view does not update until you relaunch the app. Here is the main view: struct ContentView: View { @Environment(\.managedObjectContext) private var viewContext @FetchRequest(sortDescriptors: [SortDescriptor(\.name)]) private var parents: FetchedResults<Parent> @State private var editingParent: Parent? var body: some View { List { ForEach(parents) { parent in VStack(alignment: .leading) { Text(parent.name ?? "No Name") Text("\(parent.children!.count) children") Text("\(getNumberOfStar(parent: parent)) Stared") } .onTapGesture { editingParent = parent } } } .sheet(item: $editingParent) { parent in ChildrenView(parent: parent) } Button { PersistenceController.shared.saveParent(name: "New Parent") } label: { Text("Add Parent") }.padding() Button { parents.forEach { parent in PersistenceController.shared.delete(parent: parent) } } label: { Text("Delete All") }.padding() } //this function does not get called when the database changes func getNumberOfStar(parent: Parent) -> Int { let children = parent.children!.allObjects as! [Child] let starred = children.filter({$0.star == true}) return starred.count } } This is the child view: @FetchRequest var children: FetchedResults<Child> var parent: Parent init(parent: Parent) { self.parent = parent _children = FetchRequest<Child>(sortDescriptors: [], predicate: NSPredicate(format: "parent = %@", parent)) } var body: some View { List { ForEach(children) { child in HStack { Text(child.name ?? "No Name") Text(child.star ? "⭐️" : "") Spacer() Button { PersistenceController.shared.updateStar(child: child) } label: { Text("Toggle Star") } } } } Button { PersistenceController.shared.saveChild(name: "New Child", parent: parent) } label: { Text("Add Child") } } } This is the Persistence Controller: static let shared = PersistenceController() let container: NSPersistentContainer init(inMemory: Bool = false) { container = NSPersistentContainer(name: "Model") if inMemory { container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null") } container.loadPersistentStores(completionHandler: { (storeDescription, error) in if let error = error as NSError? { // Replace this implementation with code to handle the error appropriately. // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. /* Typical reasons for an error here include: * The parent directory does not exist, cannot be created, or disallows writing. * The persistent store is not accessible, due to permissions or data protection when the device is locked. * The device is out of space. * The store could not be migrated to the current model version. Check the error message to determine what the actual problem was. */ fatalError("Unresolved error \(error), \(error.userInfo)") } }) container.viewContext.automaticallyMergesChangesFromParent = true } func save() { let viewContext = container.viewContext do { try viewContext.save() } catch { /** Real-world apps should consider better handling the error in a way that fits their UI. */ let nsError = error as NSError fatalError("Failed to save Core Data changes: \(nsError), \(nsError.userInfo)") } } func saveParent(name: String) { let newParent = Parent(context: container.viewContext) newParent.name = name save() } func saveChild(name: String, parent: Parent) { let newChild = Child(context: container.viewContext) newChild.name = name newChild.parent = parent newChild.star = false save() } func updateStar(child: Child) { child.star.toggle() save() } func delete(parent: Parent) { let viewContext = container.viewContext viewContext.delete(parent) save() } } Finally here is the App struct: struct Core_Data_Child_Save_ExampleApp: App { let persistenceController = PersistenceController.shared var body: some Scene { WindowGroup { ContentView() .environment(\.managedObjectContext, persistenceController.container.viewContext) } } }
4
0
338
Dec ’24