OK, it's as small and tight as I can make it.
When I change a property of an object in an array that belongs to another object, the data changes but SwiftUI doesn't reliably update the view.
In my example code:
- create a new schedule
- click the new schedule
- on the next view, change it's name
- go back
The view won't update unless you make another schedule. That will kick the List().
How can I make it just update when the name has changed?
Thanks for your help!
Views
import SwiftUI
import CoreData
struct ContentView: View {
@ObservedObject var allSchedules:AllSchedules = AllSchedules.shared
var body: some View {
NavigationView {
List {
ForEach (allSchedules.schedules) { s in
NavigationLink(destination: EditView(schedule: s), label: {Text(s.name)} )
}
Button("Add a Schedule") { allSchedules.schedules.append(Schedule()) }
}
}
}
}
struct EditView: View {
@State var schedule:Schedule
var body: some View {
List {
TextField("Change Name", text: $schedule.name, prompt: Text("Change Name"))
}
}
}
Classes
class AllSchedules:ObservableObject {
@Published var schedules:Array<Schedule> = []
static let shared = AllSchedules()
private init() {}
}
class Schedule: ObservableObject, Identifiable {
let id = UUID()
@Published var name:String = "New Schedule"
}
I didn't write this, comes with new projects. Won't work without it. Necessary boilerplate stuff.
import CoreData
@main
struct Timer3App: App {
let persistenceController = PersistenceController.shared
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, persistenceController.container.viewContext)
}
}
}
struct PersistenceController {
static let shared = PersistenceController()
static var preview: PersistenceController = {
let result = PersistenceController(inMemory: true)
let viewContext = result.container.viewContext
for _ in 0..<10 {
let newItem = Item(context: viewContext)
newItem.timestamp = Date()
}
do {
try viewContext.save()
} catch {
// 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.
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
return result
}()
let container: NSPersistentContainer
init(inMemory: Bool = false) {
container = NSPersistentContainer(name: "Timer3")
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
}
}
The PesistentController is struct but your Schedule and AllSchedule are class. The SwiftUI can observe a struct changes but the class instance is only a pointer in the memory and the SwiftUI doesn't known when the instance changes. When you define an instance of class, you have to create with @StateObject, and when you want to refer to this instance you can use as @EnvironmentObject.
So I think this have to work:
And I think you have to convert the Schedule to struct, because when a Schedule is an instace of class, the changes of "@State var schedule:Schedule" won't work because of above reasons.