NavigationView only updates going forward, not back

66 Views Asked by At

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
    }
}
1

There are 1 best solutions below

0
On

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:

import CoreData

@main
struct Timer3App: App {
    let persistenceController = PersistenceController.shared
    @StateObject var allSchedules:AllSchedules = AllSchedules.shared

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.managedObjectContext,    persistenceController.container.viewContext)
                .environmentObject(allSchedules)
        }
    }
}

struct PersistenceController {
    ...
}

struct ContentView: View {
    @EnvironmentObject var allSchedules

    var body: some View {
        ...
    }
}

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.

struct Schedule: Identifiable {
    let id = UUID()
    var name:String = "New Schedule"
}

struct EditView: View {
    @State var schedule:Schedule

    var body: some View {
        ...
    }
}