0

Here is a demo of what I have (kind of a lot of code, but I hope someone can follow it).
I have one entity inside Core Data named Activity with one string field. For that I use this extension to display the data in the Previews:

extension Activity {
    var _name: String {
        name ?? ""
    }
    
    static var example: Activity {
        let controller = DataController(inMemory: true)
        let viewContext = controller.container.viewContext

        let activity = Activity(context: viewContext)
        activity.name = "running"
        
        return activity
    }
}

For setting up Core Data I use a DataController object:

class DataController: ObservableObject {
    let container: NSPersistentCloudKitContainer
    
    init(inMemory: Bool = false) {
        container = NSPersistentCloudKitContainer(name: "Model")
        
        if inMemory {
            container.persistentStoreDescriptions.first?.url = URL(fileURLWithPath: "/dev/null")
        }
        
        container.loadPersistentStores { storeDescription, error in
            if let _ = error {
                fatalError("Fatal error loading store")
            }
        }
    }
    
    static var preview: DataController = {
        let dataController = DataController(inMemory: true)
        let viewContext = dataController.container.viewContext
        
        do {
            try dataController.createSampleData()
        } catch {
            fatalError("Fatal error creating preview")
        }
        
        return dataController
    }()
    
    func createSampleData() throws {
        let viewContext = container.viewContext
        
        for _ in 1...10 {
            let activity = Activity(context: viewContext)
            activity.name = "run"
        }
        
        try viewContext.save()
    }
}

In the app file I do the following setup:

struct TestApp: App {
    @StateObject var dataController: DataController
    @Environment(\.managedObjectContext) var managedObjectContext
    
    init() {
        let dataController = DataController()
        _dataController = StateObject(wrappedValue: dataController)
    }
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.managedObjectContext, dataController.container.viewContext)
        }
    }
}

In my ContentView I display a list of this string from Core Data, which works correctly:

struct ContentView: View {
    let activities: FetchRequest<Activity>
    
    init() {
        activities = FetchRequest<Activity>(entity: Activity.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \Activity.name, ascending: false)], predicate: nil)
    }
    
    var body: some View {
        List {
            ForEach(activities.wrappedValue) { activity in
                ActivityView(activity: activity)
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var dataController = DataController.preview
    
    static var previews: some View {
        ContentView()
            .environment(\.managedObjectContext, dataController.container.viewContext)
            .environmentObject(dataController)
    }
}

But in my ActivityView where I display the string in a simple text field, previewing doesn't work.

struct ActivityView: View {
    let activity: Activity
    
    init(activity: Activity) {
        self.activity = activity
    }
    
    var body: some View {
        Text(activity._name)
    }
}

struct ActivityView_Previews: PreviewProvider {
    static var previews: some View {
        ActivityView(activity: Activity.example)
    }
}

I can see the string "run" in my list, 10 times, the way it is setup, but in the ActivityView screen I don't see anything displayed in the preview.
Not sure why is that, I hope someone has an idea.

edit:

I also tried this in the preview, but still doesn't work.

struct ActivityView_Previews: PreviewProvider {
    static var dataController = DataController.preview
    
    static var previews: some View {
        ActivityView(activity: Activity(context: dataController.container.viewContext))
            .environment(\.managedObjectContext, dataController.container.viewContext)
    }
}
5
  • Does this answer your question stackoverflow.com/a/62898993/12299030? Commented Apr 11, 2022 at 16:19
  • I can't use that, because there is no AppDelegate for me, but I think my code is similar to that. I added the modified code at the end of the question with the alternative I tried which I think is similar to your sugestion Commented Apr 11, 2022 at 16:48
  • You should pass the viewContext that you created in preview to the createSampleData func, otherwise you are using two different contexts. Commented Apr 11, 2022 at 16:52
  • @Yrb: ok, I tried that, but still behaves the same Commented Apr 11, 2022 at 17:05
  • Does this answer your question? PreviewProvider and ObservedObject properties Commented Apr 11, 2022 at 23:41

1 Answer 1

2

In SwiftUI we use the View hierarchy to convert from the rich model types to simple types. So the best way to solve this is to redesign ActivityView to work with simple types rather than the model type then it would be previewable without creating a managed object. I recommend watching Structure your app for SwiftUI previews which covers this technique and offers a few others like protocols and generics.

Btw I also noticed this problem:

init() {
    let dataController = DataController()
    _dataController = StateObject(wrappedValue: dataController)
}

StateObject init uses @autoclosure, e.g.

@inlinable public init(wrappedValue thunk: @autoclosure @escaping () -> ObjectType)

This means the object init needs to be inside the brackets, e.g.

_dataController = StateObject(wrappedValue: DataController())

This is what prevents the object from being init over and over again every time SwiftUI recalculates the View hierarchy.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.