Previewing ContentView with CoreData

2020-05-21 07:39发布

问题:

When I try to a SwiftUI ContentView that contains a CoreData fetch request, the preview crashes. Wondering what the correct way to setup the @environment so the preview can access the coredata stack. This works fine when building to a simulator but not with a PreviewProvider

import SwiftUI

struct ContentView: View {
    @Environment(\.managedObjectContext) var managedObjectContext

    @FetchRequest(entity: ProgrammingLanguage.entity(), sortDescriptors: [
            NSSortDescriptor(keyPath: \ProgrammingLanguage.name, ascending: true),
            NSSortDescriptor(keyPath: \ProgrammingLanguage.creator, ascending: false)
        ]) var languages: FetchedResults<ProgrammingLanguage>

    var body: some View {
        NavigationView {
            List {
                ForEach(languages, id: \.self) { language in
                    Text("Language: \(language.name ?? "Anonymous")")
                }
            }
            .navigationBarTitle("My Languages")
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

When I try to pass in argument to the ContentView in ContentView_Previews like so I get the following compiler error.

ContentView(managedObjectContext: managedObjectContext)

Error: Instance member 'managedObjectContext' cannot be used on type 'ContentView_Previews'

Maybe this isn't supported by SwiftUI previews yet? Or is there anything that could fix this?

I'm running Xcode 11 Beta 7.

回答1:

Credit goes to @ShadowDES - in the Master/Detail template project in Xcode Beta 7 there's a preview that uses Core Data:

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

This works for me in Xcode Beta 5 (my Beta 7 crashes)



回答2:

The accepted answer does not work for me.

The essence of the problem is that Preview seems to start with it's own (empty!) persistent store, so you must somehow populate that store with enough objects for all of your Previews to work. I created a class function that populates my database with sample objects IF the database is empty. Also, for each Entity in my model I created a static property that return one of those sample objects to pass as a parameter, as needed, for the specific View being previewed.

This simplifies the Preview code if the previews use a lot of managed objects:

struct StringAttributeView_Previews: PreviewProvider {  
    static var previews: some View {
        Preview.database()
        return NavigationView {
            StringAttributeView(attribute: Preview.attribute)
        }
    }
}

Here is an edited example of my Preview class. Obviously, it is specific to my DataModel class and would have to be tailored to your unique data model, but it should give you the idea of what is needed.

class Preview {

    //MARK: - Populate Preview's Core Data Database
    class func database() {
        if DataModel.isDatabaseEmpty() {
            McDocument.loadSampleData()
        }
    }

    //MARK: - For Previews
    class var attribute:
        Attribute { get { return DataModel.allObjects(for: "Attribute").first as! Attribute } }
}