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)
}
}
"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:
Now this static property is on your model that it creates mock data for.
How to use
(Reference: "SwiftData Mastery in SwiftUI" book)
Since
preview
returns aModelContainer
, you can use it with themodelContainer
modifier to power your previews with mock data!