Using Core Data with SwiftUI App Protocol

Is it possible to use CoreData with the newly announces SwiftUI App Protocol for 100% SwiftUI apps. If I need to create an app with persistant storage, is there a way to achieve this with the new protocol? I like the idea of having my app fully compatable across all systems.

Thanks.
Answered by mtsrodrigues in 615285022
Yes, you can setup everything you need directly in your App as following:

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)")
            }
        }
    }
}

Accepted Answer
Yes, you can setup everything you need directly in your App as following:

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)")
            }
        }
    }
}

Thanks @mtsrodrigues I also was trying to find out how to use CoreData with a Pure Cross-platform SwiftUI App
Is it also possible to implement CoreData Integration with iCloud, like its provided by the standard Xcode templates?
@mtsrodrigues is there any more documentation on this? I don't quit understand all of what's going on in your example. Where is it defining the CoreData store? How do you save after certain actions?
@keelay The store is being created lazily - when it's injected into the environment. As far as saving after certain actions, you can use the MOC in SwiftUI just as you would in a UIKit or AppKit app. This example also saves when the scene goes into the background state.
@micho2, to achieve CloudKit integration you should be able to change the persistentContainer property like so...

Code Block
    var persistentContainer: NSPersistentContainer = {       
let container = NSPersistentCloudKitContainer(name: "SampleApp")      // change this line 
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {               
fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
container.viewContext.automaticallyMergesChangesFromParent = true // add this line
        return container
    }()

although I've not tested this yet!
Super helpful @mtsrodrigues. Is it possible to abstract the persistent container behind a store class that handles adding new items, saves, deletes, etc., and pass that into the environment and update the UI based on changes to the store? I've been trying to make such a thing work with SwiftUI, but have so far been running into lots of roadblocks (but it's probably more my lack of experience with CoreData, than anything else).
Thank you. It can be useful to show its integration into Xcode templates by @Apple!
Using this info that has been provided, what do I need to add to the view that should be saving the data to Core Data

Is it still
Code Block
@Environment(\.managedObjectContext) var moc


I’m also interested in learning how to put this into a store class which is storing to Core Data behind the scenes. I have this working myself, but I’m not sure it’s the right way to do it and it’d be great to follow the lead of someone more experienced and/or Apple.
@ryanbdevilled I'm doing that with singleton pattern (in order to access to persistent store from UIKit views).

Code Block
final class PersistentStore {
let shared = PersistentStore()
lazy var persistentStore = { ... }
func saveContext() { ... }
}


Code Block
var body: some Scene {       
WindowGroup {           
MovieList()
.environment(\.managedObjectContext, PersistentStore.shared)
}
}


It is important to call to PersistentStore.shared from anywhere in your app (because it is instantiated yet), not PersistentStore(). If you are calling that from SwiftUI views, obviously you can directly from @environmentObject.


Regarding  mtsrodrigues's solution, how would one get to the viewContext property elsewhere in the app?

I'm asking because I want to be able to run a preview but don't know what to give to the PreviewProvider. You can't use (UIApplication.shared.delegate as! AppDelegate).persistentContainer because there isn't an app delegate in this case.

And if you do use an app delegate, you have to be calling UIApplication, which means you lose cross-platform Mac support, or you have to do an @available dance, I guess?
@davextreme You don't need AppDelegate to instantiate the persistentContainer in SwiftUI, as he explained. And in my last response you can get the solution for get viewContext working elsewhere in your app (with singleton pattern).
A warning about a mistake in mtsrodrigues's answer. It will recreate the persistent container every time the struct is recreated. Instead you are supposed to use @StateObject to prevent this.
Thanks @AlbertUI. If I'm understanding your code correctly you're putting the entire PersistentStore class singleton instance into the managedobjectcontext key path, as opposed to only the context?

So I assume you can have all manner of helper methods in your PersistentStore class and call those through persistentStore.shared.saveNewObject(), persistentStore.shared.fetchObjects(named: "foo"), etc?

When I was playing around earlier, I was only storing the context under the managedobjectcontext key path, but I never thought it would be possible to put the whole wrapper class into the key path...
@ryanbdevilled Yes, you can. This pattern is used by Apple in their libraries such as NotificationCenter.default (->"default" is the singleton), DispatchQueue.main (->main is the singleton), UserDefaults.standard (->standard is the singleton).

This pattern helps to have a single instance of an object shared across all your app. And of course you can code another functions in order to help your persistentObject, like func saveContext(), func deleteAll(), etc. The possibilities are infinity.

I use this pattern to have 1 record of entity shared across all my app, this way:

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) {
}
}


And whenever that patient changes all my SwiftUI views that are observing the "patient" variable reloads automatically. If you want reload your conventional UIKit/AppKit views you can do this also:

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


@mtsrodrigues:
There is an error at line:
.environment(\.managedObjectContext, persistentContainer.viewContext)

Error: "Cannot use mutating getter on immutable value: 'self' is immutable"

Nicolae

Thanks @AlbertUI. I like your implementation in a singleton pattern. However I get the following error:

Code Block
MainView().environment(\.managedObjectContext, PersistentStore.shared)

Error: > Cannot convert value of type 'PersistentStore' to expected argument type 'NSManagedObjectContext'

My implementation looks like this:

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)")
            }
        }
    }
}


@AlbertUI Hmm, I tried your approach, and tried passing PersistentStore.shared into the environment
Code Block
var body: some Scene {
        WindowGroup {
            MovieList()
.environment(\.managedObjectContext, PersistentStore.shared)
}
}

But I got an error saying that it could not convert type PersistentStore to expected argument type NSManagedObjectContext. Any ideas? PersistentStore.shared would be type PersistentStore, which wouldn't work if you need to pass in a NSManagedObjectContext, right?
@DannyCamenisch @ryanbdevilled  Add this variable to the PersistentStore:

Code Block swift
var context: NSManagedObjectContext {
return persistentContainer.viewContext
}


Then you can pass this in the environment as follows:

Code Block swift
var body: some Scene {       
WindowGroup {           
MovieList()
.environment(\.managedObjectContext, PersistentStore.shared.context)
}
}




Thanks @mtsrodrigues and other contributors. I am developing an app that uses UIManagedDocument in conjunction with UIDocumentBrowserViewController and would like to migrate this to SwiftUI. It sees to me as though the new DocumentGroup stuff might be coaxed into using the same approach. Any ideas?

@DannyCamenisch, @ryanbdevilled - I think it should be PersistentStore.shared.persistentContainer.viewContext, which is an NSManagedObjectContext, instead of just PersistentStore.shared.

e.g.
Code Block
MainView().environment(\.managedObjectContext, PersistentStore.shared.persistentContainer.viewContext)

I think a few people have suggested something similar to what m2ike suggested:
Code Block
MainView().environment(\.managedObjectContext, PersistentStore.shared.persistentContainer.viewContext)


It certainly works to read the viewContext from the PersistentStore. However, speaking only for myself, I'm looking for a way to create a wrapper class around the NSPersistentContainer instance that can be passed around the entire view hierarchy, and not only pass around the viewContext. The reason for this is that I want custom methods for saving, creating new entity objects, fetching specific sets entities, etc. In order to do this, there seem to be at least 4 different approaches:
  1. 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]

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

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

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

@nicolaeturcan

There is an error at line: .environment(\.managedObjectContext, persistentContainer.viewContext) Error: "Cannot use mutating getter on immutable value: 'self' is immutable"

Did you use lazy var persistentContainer? If so, then making it non-lazy should work.


@42Wowbaggers

Did you use lazy var persistentContainer? If so, then making it non-lazy should work.

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.

IMO the best approach would be to move the initialisation of the container into a private init()—so the store can only be accessed through the shared singleton—and make persistentContainer immutable.

Code Block swift
class PersistentStore {
static let shared = PersistentStore()
let persistentContainer: NSPersistentContainer
private init() {
self.persistentContainer = // ...
}
}


Using Core Data with SwiftUI App Protocol
 
 
Q