Thanks.
Using Core Data with SwiftUI App Protocol
Thanks.
Code Block swift @main struct SampleApp: App { @Environment(\.scenePhase) private var scenePhase var body: some Scene { WindowGroup { MovieList() .environment(\.managedObjectContext, persistentContainer.viewContext) } .onChange(of: scenePhase) { phase in switch phase { case .active: print("active") case .inactive: print("inactive") case .background: print("background") saveContext() } } } var persistentContainer: NSPersistentContainer = { let container = NSPersistentContainer(name: "SampleApp") container.loadPersistentStores(completionHandler: { (storeDescription, error) in if let error = error as NSError? { fatalError("Unresolved error \(error), \(error.userInfo)") } }) return container }() func saveContext() { let context = persistentContainer.viewContext if context.hasChanges { do { try context.save() } catch { let nserror = error as NSError fatalError("Unresolved error \(nserror), \(nserror.userInfo)") } } } }
Code Block final class PersistentStore: ObservableObject { @Published var patient: Patient init(patientUUID: UUID) { /* Fetch request, get the patient with that UUID, etc ... */ } func changePatient(to: UUID) { } }
Code Block var cancellableBag = Set<AnyCancellable>() override func viewDidLoad { PersistentStore.shared.patient.objectWillChange.sink { _in reloadView() /* Or other implementations of what you want to do */ }.store(in: &cancellableBag) }
Code Block MainView().environment(\.managedObjectContext, PersistentStore.shared)
Code Block import Foundation import CoreData final class PersistentStore: ObservableObject { @Published var accounts = [Account]() static let shared = PersistentStore() private init() { fetchEntries() } func fetchEntries() { let moc = persistentContainer.viewContext let request: NSFetchRequest<Account> = Account.fetchRequest() do { let accountsCoreData = try moc.fetch(request) accounts = accountsCoreData } catch { print("Fetch failed: Error \(error.localizedDescription)") accounts = [] } } var persistentContainer: NSPersistentContainer = { let container = NSPersistentContainer(name: "Model") container.loadPersistentStores(completionHandler: { (storeDescription, error) in if let error = error as NSError? { fatalError("Unresolved error \(error), \(error.userInfo)") } }) return container }() func saveContext() { let context = persistentContainer.viewContext if context.hasChanges { do { try context.save() } catch { let nserror = error as NSError fatalError("Unresolved error \(nserror), \(nserror.userInfo)") } } } }
Code Block var body: some Scene { WindowGroup { MovieList() .environment(\.managedObjectContext, PersistentStore.shared) } }
Code Block swift var context: NSManagedObjectContext { return persistentContainer.viewContext }
Code Block swift var body: some Scene { WindowGroup { MovieList() .environment(\.managedObjectContext, PersistentStore.shared.context) } }
Code Block MainView().environment(\.managedObjectContext, PersistentStore.shared.persistentContainer.viewContext)
Code Block MainView().environment(\.managedObjectContext, PersistentStore.shared.persistentContainer.viewContext)
Pass the context through the \.managedObjectContext environment key, and recreate all the save, creating, fetching functions wherever you read the context [Obviously this would be a silly approach, with a lot of duplicated code prone to errors]
Create a helper class that contains all the save, creating, fetching functions, and pass that into the environment, while separately passing around the viewContext as described above. [This works, but it would be better to pass the context and the helper functions together].
Pass the store instance into the environment as an environmentObject, but not using the \.managedObjectContext key [This seems like it should work, but the app kept crashing when it tried to read the fetched objects from the store - most likely I was doing something wrong.]
Something like what AlbertUI proposed, but with some (currently unknown?) tweak to address the problem that the \.managedObjectContext key requires an NSManagedObjectContext object instead of a store singleton.
Did you use lazy var persistentContainer? If so, then making it non-lazy should work.There is an error at line: .environment(\.managedObjectContext, persistentContainer.viewContext) Error: "Cannot use mutating getter on immutable value: 'self' is immutable"
While this will indeed silence the error, it'll also mean that a new persistent container is created each time persistentContainer is accessed. lazy is used to make sure the closure is only ever executed once.Did you use lazy var persistentContainer? If so, then making it non-lazy should work.
Code Block swift class PersistentStore { static let shared = PersistentStore() let persistentContainer: NSPersistentContainer private init() { self.persistentContainer = // ... } }
mtsrodrigues for how to deal with change of Scene in his answer in this thread.
KrakenDev's article on how to write a one line singleton
Code Block import SwiftUI @main struct YourApp: App { @Environment(\.scenePhase) private var scenePhase @StateObject private var persistentStore = PersistentStore.shared var body: some Scene { WindowGroup { ContentView() .environment(\.managedObjectContext, persistentStore.context) } .onChange(of: scenePhase) { phase in switch phase { case .active: print("\(#function) REPORTS - App change of scenePhase to ACTIVE") case .inactive: print("\(#function) REPORTS - App change of scenePhase to INACTIVE") case .background: print("\(#function) REPORTS - App change of scenePhase to BACKGROUND") savePersistentStore() @unknown default: fatalError("\(#function) REPORTS - fatal error in switch statement for .onChange modifier") } } } func savePersistentStore() { persistentStore.save() } }
Code Block import SwiftUI import CoreData class PersistentStore: ObservableObject { var context: NSManagedObjectContext { persistentContainer.viewContext } // One line singleton static let shared = PersistentStore() // Mark the class private so that it is only accessible through the singleton `shared` static property private init() {} private let persistentStoreName: String = "YourPersistentStoreName" // MARK: - Core Data stack lazy var persistentContainer: NSPersistentContainer = { let container = NSPersistentContainer(name: persistentStoreName) // OR - Include the following line for use with CloudKit - NSPersistentCloudKitContainer // let container = NSPersistentCloudKitContainer(name: persistentStoreName) // Enable history tracking // (to facilitate previous NSPersistentCloudKitContainer's to load as NSPersistentContainer's) // (not required when only using NSPersistentCloudKitContainer) guard let persistentStoreDescriptions = container.persistentStoreDescriptions.first else { fatalError("\(#function): Failed to retrieve a persistent store description.") } persistentStoreDescriptions.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey) persistentStoreDescriptions.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey) container.loadPersistentStores(completionHandler: { (storeDescription, error) in if let error = error { // Replace this implementation with code to handle the error appropriately. fatalError("Unresolved error \(error)") } }) // Include the following line for use with CloudKit - NSPersistentCloudKitContainer container.viewContext.automaticallyMergesChangesFromParent = true // Include the following line for use with CloudKit and to set your merge policy, for example... container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy return container }() // MARK: - Core Data Saving and "other future" support (such as undo) func save() { let context = persistentContainer.viewContext if !context.commitEditing() { NSLog("\(NSStringFromClass(type(of: self))) unable to commit editing before saving") } if context.hasChanges { do { try context.save() } catch { // Customize this code block to include application-specific recovery steps. let nserror = error as NSError NSApplication.shared.presentError(nserror) } } } }
Code Block struct YourSwiftUIStruct_Previews: PreviewProvider { static var previews: some View { let context = PersistentStore.shared.context return YourSwiftUIStruct() .environment(\.managedObjectContext, context) } }