Search code examples
swiftswiftdata

#Preview crash with @Bindable


When uncomment TextField("Name:", text: $editedModel.name) canvas preview crashes with no log...

@Model
final class Item {
    var name: String
    var value: Double?
    var isCheckable: Bool = true
    
    init(name: String, value: Double? = nil) {
        self.name = name
        self.value = value
    }
}

struct EditItemView: View {
    @Bindable var editedModel: Item
    @State var temp: String = ""
    private let editModelContext: ModelContext
    
    init(item: Item, container: ModelContainer){
        editModelContext = ModelContext(container)
        editModelContext.autosaveEnabled = false
        editedModel = editModelContext.model(for: item.id) as! Item
    }
    
    var body: some View {
        VStack{
            TextField("Name:", text: $temp)
//            TextField("Name:", text: $editedModel.name)
            Toggle("Checkable", isOn: $editedModel.isCheckable)
        }
            .onDisappear{
                try! editModelContext.save()
            }
    }
}

#Preview {
    EditItemView(item: Item(name:"Preview item"), container: try! ModelContainer(for:Item.self))
}

Any idea why?


Solution

  • This seems to be caused by an (old) bug and can either be resolved as in this answer or another solution is to give name a default value in the model since

    var name: String = ""
    

    but if you don't want to change the model for a Preview/SwiftData bug a third option that is a bit more work but that might be more useful in the long run is to create a specific type to create and hold a ModelContainer instance for all your preview use.

    final class PreviewSingleton: Sendable {
        static let shared = PreviewSingleton()
        let container: ModelContainer
    
        private init() {
            let config = ModelConfiguration(isStoredInMemoryOnly: true)
            container = try! ModelContainer(for: Item.self, configurations: config)
    
            buildTestData()
        }
    
        private func buildTestData() {
            let context = ModelContext(container)
            let item = Item(name:"Preview item")
            context.insert(item)
            try? context.save()
        }
    
        func fetchFirst() -> Item? {
            let context = ModelContext(container)
            return try? context.fetch(FetchDescriptor<Item>()).first
        }
    }
    

    Note that I create a configuration for in-memory use rather and also note that if you want to add more model types the fetchFirst and similar fetch functions can easily be made generic.

    Then use this singleton (feel free to give it a better name :)) in the preview

    #Preview {
        if let item = PreviewSingleton.shared.fetchFirst() {
            return EditItemView(item: item, container: PreviewSingleton.shared.container)
        } else {
            return Text("No test data")
        }
    }