Setting the Core Data context on SwiftUI Preview in Xcode 12

Hi,

Previously to get the canvas working with Core Data-based apps, one had to inject the Core Data context via an environment variable into the PreviewProvider like so:

Code Block
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
         let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
         return ContentView().environment(\.managedObjectContext, context)
     }
 }


However, in Xcode 12 we have ditched the AppDelegate for an @main-based App.

I've setup my new @main App like so:

Code Block
@main
struct MyCoolApp: App {
    @Environment(\.scenePhase)
    private var scenePhase
    var body: some Scene {
        WindowGroup {
            ContentView()
                .frame(idealWidth: 480, idealHeight: 600)
                .environment(\.managedObjectContext, persistentContainer.viewContext)
        }
        .onChange(of: scenePhase) { (newScenePhase) in
            switch newScenePhase {
            case .background:
                saveContext()
            default:
                break
            }
        }
    }
    var persistentContainer: NSPersistentContainer = {
        let container = NSPersistentContainer(name: "MyCoolAppData")
        container.loadPersistentStores { (description, error) in
            if let error = error {
                print(error)
            }
        }
        return container
    }()
    func saveContext() {
        let context = persistentContainer.viewContext
        if context.hasChanges {
            do {
                try context.save()
            } catch {
                print(error)
            }
        }
    }
}

and created the accompanying MyCoolAppData.xcdatamodeld data model file.

How would I reference the persistentContainer from my App file, in ContentView's PreviewProvider now that you can't reference UIApplication.shared.delegate any more?

Thanks!

Accepted Reply

You could wrap your persistent container in a new struct and expose the view context as a static property. You should then be able to access it from both your App and your preview providers.

Code Block swift
struct CoreDataStack {
static var context: NSManagedObjectContext {
        return persistentContainer.viewContext
    }
static var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "MyCoolAppData")
container.loadPersistentStores { (description, error) in
if let error = error {
                print(error)
            }
        }
        return container
    }()
    func saveContext() { ... }
}

In your app:
Code Block swift
var body: some Scene {
    WindowGroup {
        ContentView()
.environment(\.managedObjectContext, CoreDataStack.context)
    }
}

Preview:
Code Block swift
static var previews: some View {
  ContentView().environment(\.managedObjectContext, CoreDataStack.context)
}


Replies

You could wrap your persistent container in a new struct and expose the view context as a static property. You should then be able to access it from both your App and your preview providers.

Code Block swift
struct CoreDataStack {
static var context: NSManagedObjectContext {
        return persistentContainer.viewContext
    }
static var persistentContainer: NSPersistentContainer = {
let container = NSPersistentContainer(name: "MyCoolAppData")
container.loadPersistentStores { (description, error) in
if let error = error {
                print(error)
            }
        }
        return container
    }()
    func saveContext() { ... }
}

In your app:
Code Block swift
var body: some Scene {
    WindowGroup {
        ContentView()
.environment(\.managedObjectContext, CoreDataStack.context)
    }
}

Preview:
Code Block swift
static var previews: some View {
  ContentView().environment(\.managedObjectContext, CoreDataStack.context)
}


This approach works in both the simulator and preview canvas but as soon as I add a fetch request, the preview canvas crashes and I get an error in the simulator saying
Code Block
executeFetchRequest:error: A fetch request must have an entity.