SwiftData Preview Crash

993 views Asked by At

I've created a basic database model in SwiftData to show an issue I'm encountering; a parent class and child class, with a relationship.

This is displayed in a simple view.

When I try and preview this in the canvas in Xcode, it crashes. If I comment out the item.parent = .preview, it doesn't crash.

Everything below (and including) the actor PreviewSampleData { is based on Apple's SwiftData code examples.

Any help would be greatly appreciated. Many thanks.

import SwiftUI
import SwiftData

struct ContentView: View {
    var child: Child
    
    var body: some View {
        VStack {
            Text("\(child.name)")
            Text("\(child.parent?.name ?? "No name")")
        }
        .padding()
    }
}

#Preview {
    ModelContainerPreview(PreviewSampleData.inMemoryContainer) {
        ContentView(child: .preview)
    }
}

@Model final class Parent {
    var name: String
    
    @Relationship(deleteRule: .cascade, inverse: \Child.parent)
    var children: [Child] = [Child]()
    
    init(name: String) {
        self.name = name
    }
}
 
extension Parent {
    static var preview: Parent {
        Parent(name: "Parent 1")
    }
}

@Model final class Child {
    var name: String
    var parent: Parent?
    
    init(name: String) {
        self.name = name
    }
}

extension Child {
    static var preview: Child {
        let item = Child(name: "Child 1")
        item.parent = .preview
        return item
    }
}

actor PreviewSampleData {

    @MainActor
    static var container: ModelContainer = {
        return try! inMemoryContainer()
    }()

    static var inMemoryContainer: () throws -> ModelContainer = {
        let schema = Schema([Parent.self, Child.self])
        let configuration = ModelConfiguration(isStoredInMemoryOnly: true)
        let container = try! ModelContainer(for: schema, configurations: [configuration])
        let sampleData: [any PersistentModel] = [
            Parent.preview, Child.preview
        ]
        Task { @MainActor in
            sampleData.forEach {
                container.mainContext.insert($0)
            }
        }
        return container
    }
}

struct ModelContainerPreview<Content: View>: View {
    var content: () -> Content
    let container: ModelContainer
    
    init(@ViewBuilder content: @escaping () -> Content, modelContainer: @escaping () throws -> ModelContainer) {
        self.content = content
        do {
            self.container = try MainActor.assumeIsolated(modelContainer)
        } catch {
            fatalError("Failed to create the model container: \(error.localizedDescription)")
        }
    }
    
    init(_ modelContainer: @escaping () throws -> ModelContainer, @ViewBuilder content: @escaping () -> Content) {
        self.init(content: content, modelContainer: modelContainer)
    }

    var body: some View {
        content()
            .modelContainer(container)
    }
}
1

There are 1 answers

0
BigMountainStudio.com On

"I want a single model container I fully populate with test data, so I can use this anywhere in my code."

Apple's solution is a bit complex.

Simple Way to Add Mock Data

Here is another way to create a container and add mock data at the same time:

@Model
class FriendModel {
    var firstName: String
    var lastName: String
    
    init(firstName: String, lastName: String) {
        self.firstName = firstName
        self.lastName = lastName
    }
}

extension FriendModel {
    // Create a static property that returns a ModelContainer
    @MainActor
    static var preview: ModelContainer {
        let container = try! ModelContainer(for: FriendModel.self,
                                            configurations: ModelConfiguration(isStoredInMemoryOnly: true))
        
        container.mainContext.insert(FriendModel(firstName: "Jason", lastName: "Barlow"))
        container.mainContext.insert(FriendModel(firstName: "Jennie", lastName: "Wilkinson"))
        container.mainContext.insert(FriendModel(firstName: "Lauren", lastName: "Brady"))
        container.mainContext.insert(FriendModel(firstName: "Matthew", lastName: "Schultz"))
        
        return container
    }
}

Now this static property is on your model that it creates mock data for.

How to use

Getting previews to work with mock data (Reference: "SwiftData Mastery in SwiftUI" book)

Since preview returns a ModelContainer, you can use it with the modelContainer modifier to power your previews with mock data!