This is the solution that allowed us to use model's with relationships and read/write from SwiftUI previews.
@lorem-ipsum's answer was very helpful but still crashed loading relationships since the model can't just be initialized, it needs to be inserted into a context to avoid crashes.
How to use
#Preview {
SwiftDataPreviewWrapper { modelContext in
MyView(
document: Document(
title: "Section one",
sections: [
Section(
content: "My content text",
)
]
).insert(modelContext) // Important to insert using the provided modelContext
)
}
}
Implementation with insert extension
/// Provides a in-memory `ModelContext` to the rendering view so `SwiftData` models can be inserted into a `ModelContext` to avoid crashes the preview.
/// While simple `SwiftData` models can be rendered in a preview without being inserted into a context at all once a `SwiftData` model accesses another
/// model via a relationship, usually a child, the preview will crash if the models do not have a context since that is how relationship models are accessed even
/// when it's all defined in memory up front.
/// Inspired by: https://mcmap.net/q/1166541/-how-do-i-preview-a-view-that-39-s-using-bindable
struct SwiftDataPreviewWrapper<Content: View>: View {
/// The content which passes in the context to the actual preview view code
let content: (ModelContext) -> Content
let modelContainer: ModelContainer
init(
for forTypes: any PersistentModel.Type = /* Suggestion: Set a global default value here for less boilerplate. */ Document.self,
@ViewBuilder content: @escaping (ModelContext) -> Content
) {
do {
// Create our model container BEFORE we render anything so it's modelContext can be used right away.
modelContainer = try ModelContainer(
for: forTypes,
configurations: .init(isStoredInMemoryOnly: true)
)
} catch {
fatalError("Could not initialize ModelContainer")
}
self.content = content
}
var body: some View {
content(modelContainer.mainContext)
.modelContainer(modelContainer)
}
}
extension PersistentModel {
/// A helper method that returns the model after inserting it into `context`.
/// Useful when we want the model right from inserting since inserting from a context is
/// more boilerplate code that makes the code less concise to read.
func insert(_ context: ModelContext) -> Self {
context.insert(self)
return self
}
}