So I have two class models, Item and Category, as follows:
@Model
class Category {
let name: String
...
}
@Model
class Item {
var name: String
var category: Category?
...
}
I have a view called ItemDetailsView
that displays all the properties of a given item. It also has a toggle that shows an ItemEditorView
where user's can edit the item properties, including the category which shows up in a picker.
I set up the preview for ItemDetailsView
as follows:
#Preview {
let preview = Preview(Item.self, Category.self)
preview.addExamples(Category.sampleCategories)
preview.addExamples(Item.sampleItems)
return ItemDetailsView(item: Item.sampleItems[0])
.modelContainer(preview.container)
}
The Preview
class sets up my model container and inserts the sample items and categories that I have instantiated as static constant variables.
struct Preview {
let container: ModelContainer
init(_ models: any PersistentModel.Type...) {
let schema = Schema(models)
let config = ModelConfiguration(isStoredInMemoryOnly: true)
do {
container = try ModelContainer(for: schema, configurations: config)
} catch {
fatalError("Could not create preview container")
}
}
func addExamples(_ examples: [any PersistentModel]) {
Task { @MainActor in
examples.forEach { example in
container.mainContext.insert(example)
}
}
}
}
I'm not sure why my preview is crashing. I had a hunch that it was because we needed all the categories to be available for the picker in ItemEditorView
but I already inserted the relevant categories into the model context. Preview is working as expected in a different view called ItemDetailsView
which lists all the items added. Navigation links and sheets work perfectly. Not sure why this is giving me trouble.
@randallle I suspect you might need to create a view to use only in previews that creates a model container before showing the preview content. This view would create the model container before displaying the preview.
I would suggest you review Adding and editing persistent data in your app sample project particularly the Preview+ModelContainer.swift
and ModelContainerPreview.swift
source file.
However, here's an example:
struct ModelContainerPreview<Content: View>: View {
var content: () -> Content
let container: ModelContainer
/// Creates an instance of the model container preview.
///
/// This view creates the model container before displaying the preview
/// content. The view is intended for use in previews only.
///
/// #Preview {
/// ModelContainerPreview {
/// AnimalEditor(animal: nil)
/// .environment(NavigationContext())
/// } modelContainer: {
/// let schema = Schema([AnimalCategory.self, Animal.self])
/// let configuration = ModelConfiguration(isStoredInMemoryOnly: true)
/// let container = try ModelContainer(for: schema, configurations: [configuration])
/// Task { @MainActor in
/// AnimalCategory.insertSampleData(modelContext: container.mainContext)
/// }
/// return container
/// }
/// }
///
/// - Parameters:
/// - content: A view that describes the content to preview.
/// - modelContainer: A closure that returns a model container.
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)")
}
}
/// Creates a view that creates the provided model container before displaying
/// the preview content.
///
/// This view creates the model container before displaying the preview
/// content. The view is intended for use in previews only.
///
/// #Preview {
/// ModelContainerPreview(SampleModelContainer.main) {
/// AnimalEditor(animal: .kangaroo)
/// .environment(NavigationContext())
/// }
/// }
///
/// - Parameters:
/// - modelContainer: A closure that returns a model container.
/// - content: A view that describes the content to preview.
init(_ modelContainer: @escaping () throws -> ModelContainer, @ViewBuilder content: @escaping () -> Content) {
self.init(content: content, modelContainer: modelContainer)
}
var body: some View {
content()
.modelContainer(container)
}
}