Using an existing Core Data database with SwiftUI Previews

For a new SwiftUI project (actually it's a new version of an existing app) I want to use an existing Core Data data model.

I wonder if there is a way to include an existing Core Data based SQLite database into the project within the PreviewProvider so that I can work with existing data while designing the user interface.

This would be much more convenient and less error-prone than handling mockup code.

Replies

What I'm doing in my example application right now, trying to learn the SwiftUI ropes, is add code to prefill the Core Data store, within AppDelegate after the app has launched - for the debug config.


It seems like SwiftUI preview mirrors the core data files created in the simulator folder to a another folder.


See this link for information on how to find this hidden folder.


It also seems that sometimes, preview will get stuck and you might need to restart Xcode or/and close open the view source window to get this to work. The whole thing seems not 100% stable right now.


Here's my preview code:


struct TeaListView_Previews: PreviewProvider {
    
    static var previews: some View {
        let moc = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
        return TeaListView().environment(\.managedObjectContext, moc)
    }
    
}


And here's what's in AppDelegate:


class func prefillDB() {
        #if DEBUG
        let shouldPrefillDB: Bool = UserDefaulwts.Default.Linkage(UserDefaults.KeysDEV.prefillDB.rawValue, nil).get() ?? false
        
        if shouldPrefillDB == true  {
            PreviewContent.prefillDB()
        }
        #endif
    }
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        
        AppDelegate.prefillDB()
        
        return true
    }

I use the following preview-specific class within Preview Content to load my model and create a fresh copy of the database inside the Caches directory, importing some sample data:


import Foundation
import CoreData
import UIKit


class PreviewManagedObjectContext {
    static let shared = try! PreviewManagedObjectContext()


    let storeCoordinator: NSPersistentStoreCoordinator
    let objectModel: NSManagedObjectModel
    let viewContext: NSManagedObjectContext


    init() throws {
        let storeURL = try FileManager.default.url(
            for: .cachesDirectory,
            in: .userDomainMask,
            appropriateFor: nil,
            create: true).appendingPathComponent("Previews.db")
        try? FileManager.default.removeItem(at: storeURL)


        objectModel = NSManagedObjectModel.mergedModel(from: nil)!
        storeCoordinator = NSPersistentStoreCoordinator(managedObjectModel: objectModel)


        try storeCoordinator.addPersistentStore(
            ofType: NSSQLiteStoreType,
            configurationName: nil,
            at: storeURL,
            options: [:])


        viewContext = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
        viewContext.persistentStoreCoordinator = storeCoordinator


        guard let url = Bundle.main.url(forResource: "todo-items", withExtension: "json") else { return }
        viewContext.performAndWait {
            try? importSampleData(from: url, to: viewContext)
        }
    }


    func backgroundContext() -> NSManagedObjectContext {
        let context = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
        context.persistentStoreCoordinator = storeCoordinator
        return context
    }
    
    var sampleList: TodoItemList {
        let request: NSFetchRequest<TodoItemList> = TodoItemList.fetchRequest()
        request.fetchLimit = 1
        request.predicate = NSPredicate(format: "name ==[c] %@", "SwiftUI Book")
        let items = try? viewContext.fetch(request)
        return items!.first!
    }
    
    var sampleItem: ToDo {
        let request: NSFetchRequest<ToDo> = ToDo.fetchRequest()
        request.fetchLimit = 1
        request.predicate = NSPredicate(format: "title ==[c] %@", "Complete SwiftUI book sample")
        let items = try? viewContext.fetch(request)
        return items!.first!
    }
}


I then use it in my previews like so:


struct TodoItemRow_Previews: PreviewProvider {
    static var previews: some View {
        let context = PreviewManagedObjectContext.shared.viewContext
        let object = PreviewManagedObjectContext.shared.sampleItem
        
        return EditModePreviewWrapper(editing: true) {
            ForEach(ColorScheme.allCases, id: \.self) { scheme in
                List {
                    TodoItemRow(item: object)
                }
                .colorScheme(scheme)
                .previewDisplayName(String(describing: scheme))
            }
        }
        .environment(\.managedObjectContext, context)
        .previewLayout(.fixed(width: 400, height: 120))
    }
}