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.
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.