Core Data in the new App Structure

What is the best way to incorporate Core Data into the new App Structure?


For example, I have waged to get it working using the following, but is this the best way to do this?
Code Block
import SwiftUI
import CoreData
@main
struct SessionsApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    var body: some Scene {
        WindowGroup {
            let context = appDelegate.persistentContainer.viewContext
            ContentView()
                .environment(\.managedObjectContext, context)
        }
    }
}

Then In AppDelegate the code is like this


Code Block
import UIKit
import CoreData
class AppDelegate: NSObject, UIApplicationDelegate {
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey : Any]? = nil) -> Bool {
        print("UIApplication, didFinishLaunchingWithOptions")
        return true
    }
    // MARK: - Core Data stack
    lazy var persistentContainer: NSPersistentContainer = {
        let container = NSPersistentContainer(name: "Sessions")
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
        return container
    }()
    // MARK: - Core Data Saving support
    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)")
            }
        }
    }
}

Finally

and lastly it is used in this View, which fails to dynamically preview but still works...
Code Block
import SwiftUI
import CoreData
struct ContentView: View {
    @Environment(\.managedObjectContext) private var context
    @FetchRequest(entity: Session.entity(), sortDescriptors: []) private var sessions: FetchedResults<Session>
    @State private var showingAddScreen:Bool = false
    var body: some View {
        NavigationView {
            List {
                ForEach(sessions, id:\.id) {session in
                    Text("\(session.name ?? "unknown")")
                }
            }
            .navigationTitle("Sessions")
            .navigationBarItems(trailing: Button("Add", action: {
                self.showingAddScreen.toggle()
            }))
        }
        .sheet(isPresented: $showingAddScreen, onDismiss: {self.showingAddScreen.toggle()}) {
            SessionFormView()
        }
    }
}
struct ContentView_Previews: PreviewProvider {
    static let coreDataHelper = CoreDataHelper()
    static let context = coreDataHelper.persistentContainer.viewContext
    static var previews: some View {
        ContentView().environment(\.managedObjectContext, context)
    }
}


Advice ?

Please let me know if there is a much better way...

cheers
don

Answered by in 616717022
Hi Don, you don't need to create a separate class that conforms to UIApplicationDelegate. Set up your Core Data stack in a separate struct (e.g. "CoreDataStack") and expose viewContext as a static variable. You can then inject it into your content view.

Code Block swift
struct CoreDataStack {
    static var viewContext: NSManagedObjectContext {
        return persistentContainer.viewContext
    }
    
    static var persistentContainer: NSPersistentContainer = {
        let container = NSPersistentContainer(name: "Sessions")
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
        return container
    }()
    static func saveContext () {...}
}


Code Block swift
@main
struct SessionsApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.managedObjectContext, CoreDataStack.viewContext)
        }
    }
}



Accepted Answer
Hi Don, you don't need to create a separate class that conforms to UIApplicationDelegate. Set up your Core Data stack in a separate struct (e.g. "CoreDataStack") and expose viewContext as a static variable. You can then inject it into your content view.

Code Block swift
struct CoreDataStack {
    static var viewContext: NSManagedObjectContext {
        return persistentContainer.viewContext
    }
    
    static var persistentContainer: NSPersistentContainer = {
        let container = NSPersistentContainer(name: "Sessions")
        container.loadPersistentStores(completionHandler: { (storeDescription, error) in
            if let error = error as NSError? {
                fatalError("Unresolved error \(error), \(error.userInfo)")
            }
        })
        return container
    }()
    static func saveContext () {...}
}


Code Block swift
@main
struct SessionsApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.managedObjectContext, CoreDataStack.viewContext)
        }
    }
}



Brilliant, thanks, I had tried it using another class but for some reason it didn't work, but I must have had something wrong.
Thanks again for your answer.. much appreciated


hmmmm, since implementing the two classes you describe, the App no longer loads the CoreData Stack.
When using the App Delegate it did actually load and the App Worked.

Here's the Trace

2020-06-27 22:20:45.210645+1200 Sessions[73503:1020937] [error] error: No NSEntityDescriptions in any model claim the NSManagedObject subclass 'Sessions.Session' so +entity is confused.  Have you loaded your NSManagedObjectModel yet ?

CoreData: error: No NSEntityDescriptions in any model claim the NSManagedObject subclass 'Sessions.Session' so +entity is confused.  Have you loaded your NSManagedObjectModel yet ?

2020-06-27 22:20:45.211159+1200 Sessions[73503:1020937] [error] error: +[Sessions.Session entity] Failed to find a unique match for an NSEntityDescription to a managed object subclass

CoreData: error: +[Sessions.Session entity] Failed to find a unique match for an NSEntityDescription to a managed object subclass
I've fixed the Error

The Following is Required
Code Block
var body: some Scene {
        WindowGroup {
            let context = CoreDataStack.viewContext
           // let context = appDelegate.persistentContainer.viewContext
            ContentView()
                .environment(\.managedObjectContext, context)
        }


It seems that the loading of the Context from the Static Variable into the Environment does not fire in the expected sequence, so that when the class ContentView is in scope, the Object is not in the Environment in time to be utilised.
Instead, by assigning the Context prior to setting in the Environment, fixes the issue.

Core Data in the new App Structure
 
 
Q