1

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

enter image description here

With data in the customisations array - Car.mock2

customisations array not being empty crashes without error message


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 Cars, and have NavigationLinks 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(..)?

2
  • Given your use of static properties this answer might be of interest. I would personally avoid using static properties for test data and instead strive to create and insert the objects either when creating the preview or the model container. Commented Apr 11, 2024 at 8:18
  • That's very interesting. So creating the first model and inserting it, then adding the sub-model arrays seems to work. I wonder why it cannot handle the multi-levelled models. Commented Apr 11, 2024 at 9:55

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.