I am using the following code to preview a view.
#Preview {
@Previewable @Query var vegetables: [Vegetable]
NavigationStack {
VegetableDetailScreen(vegetable: vegetables[0])
}.modelContainer(previewContainer)
}
But when I run this I get index out of range. This maybe because when the VegetableDetailScreen is rendered then at that time vegetables[0] has not loaded yet.
How can I make sure that vegetables are loaded before VegetableDetailScreen screen?
My previewContainer looks like this:
@MainActor
let previewContainer: ModelContainer = {
do {
let container = try ModelContainer(for: Vegetable.self, MyGardenVegetable.self, configurations: ModelConfiguration(isStoredInMemoryOnly: true))
let vegetables = PreviewData.loadVegetables()
for vegetable in vegetables {
let vegetableModel = Vegetable(vegetableDTO: vegetable)
container.mainContext.insert(vegetableModel)
}
return container
} catch {
fatalError("Failed to create container.")
}
}()
UPDATE:
If I add if let then the view never gets rendered.
#Preview {
@Previewable @Query var vegetables: [Vegetable]
NavigationStack {
if let vegetable = vegetables.first {
VegetableDetailScreen(vegetable: vegetable)
}
}.modelContainer(previewContainer)
}
The @Query
in this case can't actually see the model container added by .modelContainer(previewContainer)
. @Previewable
wraps everything into its own view:
Tagging a variable declaration at root scope in your
#Preview
body with ‘@Previewable’ allows you to use dynamic properties inline in previews. The#Preview
macro will generate an embedded SwiftUI view; tagged declarations become properties on the view, and all remaining statements form the view’s body.
Expanding the #Preview
macro, the code looks like this:
static func makePreview() throws -> DeveloperToolsSupport.Preview {
DeveloperToolsSupport.Preview {
struct __P_Previewable_Transform_Wrapper: SwiftUI.View {
@Query var vegetables: [Vegetable]
var body: some SwiftUI.View {
NavigationStack {
VegetableView(vegetable: vegetables[0])
} .modelContainer(previewContainer)
}
}
return __P_Previewable_Transform_Wrapper()
}
}
As you can see, modelContainer
modifies the NavigationStack
only, instead of modifying the __P_Previewable_Transform_Wrapper
, so @Query
doesn't know about the model container.
You can instead inject the model container using a PreviewModifier
. Here is an example, adapted from the one shown in the documentation page.
struct SampleData: PreviewModifier {
// make your previewContainer here
static func makeSharedContext() throws -> ModelContainer {
let container = try ModelContainer(for: Vegetable.self, configurations: ModelConfiguration(isStoredInMemoryOnly: true))
container.mainContext.insert(Vegetable(name: "Lettuce"))
return container
}
func body(content: Content, context: ModelContainer) -> some View {
content.modelContainer(context)
}
}
// PreviewModifier is not available before these versions, but since this is preview,
// this works even if you need to support earlier versions
@available(iOS 18, macOS 15, *)
#Preview(traits: .modifier(SampleData())) {
@Previewable @Query var vegetables: [Vegetable]
NavigationStack {
VegetableView(vegetable: vegetables[0])
}
}
// ---- Model class and VegetableView ----
@Model
class Vegetable {
var name = ""
init(name: String = "") {
self.name = name
}
}
struct VegetableView: View {
let vegetable: Vegetable
var body: some View {
Text(vegetable.name)
}
}