Updating a Core Data object does not update the SwiftUI view

Hi, guys. When I update a value in a Core Data object, the view is not updated to show the change. I don't understand what I'm doing wrong, since this worked before. Right now I have Xcode 16 Beta installed, but it happens with Xcode 15. Here is an example.

import SwiftUI
import CoreData

struct ContentView: View {
    @Environment(\.managedObjectContext) private var viewContext
   @State private var selectedItem: Item?

    var body: some View {
        NavigationStack {
           VStack {
              Text("Year: \(selectedItem?.year ?? 0)")
                 .onTapGesture {
                    changeYear()
                 }
              Spacer()
           }
           .onAppear {
              // Load an item from the database
              let request: NSFetchRequest<Item> = Item.fetchRequest()
              request.fetchLimit = 1
              
              if let item = try? viewContext.fetch(request).first {
                 selectedItem = item
              }
           }
        }
    }

    private func changeYear() {
       let year = Int16.random(in: 1900..<2020)
       print("Random year: \(year)")
      
       selectedItem?.year = year
       try? viewContext.save()
    }
}

#Preview {
    ContentView()
      .environment(\.managedObjectContext, PersistenceController.preview.container.viewContext)
}

I think it is easy to follow. There is an entity called Item with an attribute called year. I preloaded a few objects for the preview. This view loads one item and assigns it to a state property. When you tap on the Text view, the changeYear() method is executed. In this method I select a random year, assign it to the object in the selectedItem property and save the context. A message is printed on the console with the new value, but the view never updates. I'm also having similar problems with SwiftData and To Many relationships. They also do not update the views when they are modified.

In case you want to test it, you can just create a new project with Core Data set in, change the name of the attribute in the Item entity to year and select Int16 as the data type, and then update the following method to preload some values.

 @MainActor
    static let preview: PersistenceController = {
        let result = PersistenceController(inMemory: true)
        let viewContext = result.container.viewContext
        for _ in 0..<10 {
            let newItem = Item(context: viewContext)
            newItem.year = Int16.random(in: 1900..<2020)
        }
        do {
            try viewContext.save()
        } catch {
            let nsError = error as NSError
            fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
        }
        return result
    }()

Thanks for any help.

Answered by DTS Engineer in 799838022

In your code example, changing selectedItem?.year doesn't trigger a SwiftUI update because, from the SwiftUI perspective, the selectedItem state does not really change.

Concretely, when SwiftUI compares the selectedItem state, which is an NSManagedObject, it compares the object pointer and the object ID (NSManagedObject.objectID). Both of them don't change when you set a new value for the year attribute.

As @newwbee suggested, you can use @FetchRequest, which observes the change on the managed object context (NSManagedObjectContext) and triggers a SwiftUI update for you. If that doesn't fit, you can observe the .NSManagedObjectContextDidSave notification and trigger a SwiftUI update with your own code, as discussed here.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

CoreData

Use @FetchRequest to keep CoreData in sync with SwiftUI view

https://developer.apple.com/wwdc21/10017?time=581

SwiftData

Use Query, to keep SwiftData in sync with your SwiftUI view.

Please watch the WWDC videos on SwiftData to see how it is meant to be used with SwiftUI views.

Reference:

Accepted Answer

In your code example, changing selectedItem?.year doesn't trigger a SwiftUI update because, from the SwiftUI perspective, the selectedItem state does not really change.

Concretely, when SwiftUI compares the selectedItem state, which is an NSManagedObject, it compares the object pointer and the object ID (NSManagedObject.objectID). Both of them don't change when you set a new value for the year attribute.

As @newwbee suggested, you can use @FetchRequest, which observes the change on the managed object context (NSManagedObjectContext) and triggers a SwiftUI update for you. If that doesn't fit, you can observe the .NSManagedObjectContextDidSave notification and trigger a SwiftUI update with your own code, as discussed here.

Best,
——
Ziqiao Chen
 Worldwide Developer Relations.

Updating a Core Data object does not update the SwiftUI view
 
 
Q