swiftui - Previewing SwiftData Records in Xcode Previews - Stack Overflow

admin2025-04-30  0

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)
} 

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)
} 
Share Improve this question edited Jan 5 at 0:41 user19037628 asked Jan 5 at 0:28 user19037628user19037628 3311 silver badge9 bronze badges 6
  • have you tried using a simple if, such as if vegetables.count > 0 {...} – workingdog support Ukraine Commented Jan 5 at 0:36
  • @workingdogsupportUkraine Yes. I added if let (see update) and the it does not even render the VegetableDetailScreen. – user19037628 Commented Jan 5 at 0:42
  • see hackingwithswift.com/quick-start/swiftdata/… – workingdog support Ukraine Commented Jan 5 at 1:11
  • Thanks! My code looks very similar to the provided link. Anyway. Don't worry I will keep looking. – user19037628 Commented Jan 5 at 1:41
  • the first line of the link, tells you in Preview "...any properties created using the @Query macro will quietly return no results.". – workingdog support Ukraine Commented Jan 5 at 1:54
 |  Show 1 more comment

1 Answer 1

Reset to default 1

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)
    }
}
转载请注明原文地址:http://anycun.com/QandA/1746023456a91484.html