Working with SwiftData and it seems when I try to access an array within the model Previews crash. There is no error message, just a black preview and the "Preview Crashed" message (opposed to the version with the diagnose or info button). The odd thing is if I use an empty array for the data then it works, or at least doesn't crash.
Without any data in the customisations
array - Car.mock1
With data in the customisations
array - Car.mock2
This is the minimum code to reproduce the error:
Models
@Model
class Car {
var id: UUID
var name: String
var customisations: [Customisation]
init(name: String, customisations: [Customisation]) {
self.id = UUID()
self.name = name
self.customisations = customisations
}
static let mock1: Car = Car(name: "My Car 1", customisations: [])
static let mock2: Car = Car(name: "My Car 2", customisations: [
Customisation(name: "Rims")
])
}
@Model
class Customisation {
var id: UUID
var name: String
init(name: String) {
self.id = UUID()
self.name = name
}
}
View / Preview
struct ContentView: View {
let car: Car
var body: some View {
Form {
Text(car.name)
Text("\(car.customisations.count)")
}
}
}
#Preview {
// SwiftDataPreviewer(preview: PreviewContainer([Car.self, Customisation.self])) <-- also crashes
SwiftDataPreviewer(preview: PreviewContainer([Car.self])) {
ContentView(car: Car.mock2) // Car.mock1 works
}
}
SwiftData Preview helpers
struct PreviewContainer {
let container: ModelContainer!
init(_ types: [any PersistentModel.Type], isStoredInMemoryOnly: Bool = true) {
let schema = Schema(types)
let configuration = ModelConfiguration(isStoredInMemoryOnly: isStoredInMemoryOnly)
do {
self.container = try ModelContainer(for: schema, configurations: configuration)
} catch {
self.container = nil
fatalError("ERROR: ModelContainer failed to initalise")
}
}
func add(items: [any PersistentModel]) {
Task { @MainActor in
items.forEach { container.mainContext.insert($0) }
}
}
}
struct SwiftDataPreviewer<Content: View>: View {
private let preview: PreviewContainer
private let items: [any PersistentModel]?
private let content: Content
init(
preview: PreviewContainer,
items: [any PersistentModel]? = nil,
@ViewBuilder _ content: @escaping () -> Content
) {
self.preview = preview
self.items = items
if let items = items { preview.add(items: items) }
self.content = content()
}
var body: some View {
content.modelContainer(preview.container)
}
}
I'm using the PreviewContainer
as it allows me to reuse it across the entire app base, and inject the array of data for lists or loops.
The only time this has become an issue is when trying to access a single item in the Preview.
What's odd though - in terms of this and previews - is if I set up a preview that injects an array of Car
s, and have NavigationLink
s to the each item ending on this ContentView
the array is accessed without a hitch.
The error only occurs accessing it in the single instance. Is this a current bug or is there a way to get it working with the above structure of injecting the .modelContainer(..)
?
Answers
It seems like there might be a compatibility issue or a bug related to how SwiftData handles previews when accessing arrays of model objects directly within a single view. One possible workaround could be to modify the SwiftDataPreviewer
to handle the case where an array of model objects is passed directly to the preview. Here's how you can do it:
struct SwiftDataPreviewer<Content: View>: View {
private let preview: PreviewContainer
private let items: [any PersistentModel]?
private let content: Content
init(
preview: PreviewContainer,
items: [any PersistentModel]? = nil,
@ViewBuilder _ content: @escaping () -> Content
) {
self.preview = preview
self.items = items
if let items = items { preview.add(items: items) }
self.content = content()
}
var body: some View {
if let items = items {
content.modelContainer(preview.container, items: items)
} else {
content.modelContainer(preview.container)
}
}
}
And then modify the modelContainer
extension on View
to handle the case where an array of model objects is passed:
extension View {
func modelContainer(_ container: ModelContainer, items: [any PersistentModel]) -> some View {
environment(\.managedObjectContext, container.mainContext)
.environment(\.container, container)
.environment(\.items, items)
}
}
Now, you should be able to use the SwiftDataPreviewer
with either a single model object or an array of model objects without encountering the crashing issue in previews.