You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftData中Picker关联Car与Refuel模型时应用崩溃问题排查

解决SwiftData中Car与Refuel关联导致的崩溃问题

我在应用中使用SwiftData定义了两个模型Car和Refuel:Car仅存储车辆名称,支持用户添加多辆车;用户记录加油信息时,需要从已保存的车辆中选择对应车型并填写其他数据,但保存模型时应用崩溃,推测是关联关系设置错误。

出现的错误

错误1:Picker选择车辆时触发

Picker: the selection "RefuelApp.Car" is invalid and does not have an associated tag, this will give undefined results.

错误2:关闭保存表单时崩溃

//In Refuel.swift
@Relationship var car: Car
{
    @storageRestrictions(accesses: _$backingData, initializes: _car)
    init(initialValue) {
        _$backingData.setValue(forKey: \.car, to: initialValue)
        _car = _SwiftDataNoType()
    }
    get {
        _$observationRegistrar.access(self, keyPath: \.car)
        return self.getValue(forKey: \.car)
    }
    set {
        _$observationRegistrar.withMutation(of: self, keyPath: \.car) {
            self.setValue(forKey: \.car, to: newValue) // <----- Thread 1: EXC_BREAKPOINT (code=1, subcode=0x1cc1641e0)
        }
    }
}

相关代码示例

RefuelEditView.swift

import SwiftUI
import SwiftData

struct RefuelEditView: View {
    @Environment(\.dismiss) private var dismiss
    @Environment(\.modelContext) private var modelContext
    
    @Query private var cars: [Car]
    @Bindable var refill: Refuel
    
    let isNew: Bool
    @State private var selectedCar: Car
    
    init(refill: Refuel, car: Car = Car(), isNew: Bool = false) {
        self.refill = refill
        self.isNew = isNew
        self.selectedCar = car
    }
    
    var body: some View {
        Form {
            Section(header: Text("Car")) {
                Picker(selection: $selectedCar, label: Text("Car")) {
                    Text("--None--").tag(nil as Car?)
                    ForEach(cars) { car in
                        Text(car.name).tag(car)
                    }
                }
            }
            Section(header: Text("Refuel details")) {
                DatePicker(selection: .init(projectedValue: $refill.timestamp), label: { Text("Date") })
                HStack{
                    LabeledContent("Odometer") {
                        TextField("km", value: $refill.odo, format: .number.grouping(.automatic))
                            .keyboardType(.decimalPad)
                    }
                }
            }
       }
       .navigationTitle(isNew ? "New Refuel" : "Refuel")
        .toolbar {
            if isNew {
                ToolbarItem(placement: .confirmationAction) {
                    Button("Done") {
                        let item = refill
                        item.car = selectedCar
                        modelContext.insert(item)
                        dismiss()
                    }
                }
                ToolbarItem(placement: .cancellationAction) {
                    Button("Cancel") {
                        modelContext.delete(refill)
                        dismiss()
                    }
                }
            }
        }
    }
}

Car.swift

import Foundation
import SwiftData

@Model
final class Car: Identifiable {
    let id = UUID()
    var name: String
    @Relationship(deleteRule: .cascade, inverse: \Refuel.car) var refuels: [Refuel]?
    
    init() {
        self.name = ""
    }
    
    init(name: String) {
        self.name = name
    }
}

Refuel.swift

import Foundation
import SwiftData

@Model
final class Refuel: Identifiable {
    let id = UUID()
    @Relationship var car: Car
    var timestamp: Date
    private var odo_backend: Double = 0
    
    init(car: Car) {
        self.car = car
        self.odo_backend = 0
        self.timestamp = .now
    }
    
    init(car: Car, timestamp: Date, odo: Double) {
        self.car = car
        self.odo_backend = odo
        self.timestamp = timestamp
    }
}

修复方案

1. 解决Picker类型不匹配问题

Picker中--None--的tag是可选类型Car?,但绑定的selectedCar是非可选Car,类型不匹配导致错误。

  • 修改selectedCar为可选类型,并调整初始化逻辑:
    @State private var selectedCar: Car?
    
    init(refill: Refuel, isNew: Bool = false) {
        self.refill = refill
        self.isNew = isNew
        self.selectedCar = isNew ? nil : refill.car
    }
    
  • 更新Picker的绑定和tag类型:
    Picker(selection: $selectedCar, label: Text("Car")) {
        Text("--None--").tag(nil as Car?)
        ForEach(cars) { car in
            Text(car.name).tag(car as Car?)
        }
    }
    

2. 修正Refuel模型的关联关系

Refuel中的car字段是非可选,但用户可能未选择车辆,导致赋值nil崩溃。同时需明确关联的逆关系:

  • 如果允许加油记录不关联车辆,将car改为可选并添加逆关系:
    @Model
    final class Refuel: Identifiable {
        let id = UUID()
        @Relationship(inverse: \Car.refuels) var car: Car?
        var timestamp: Date
        private var odo_backend: Double = 0
        
        init() {
            self.timestamp = .now
        }
        
        init(car: Car?, timestamp: Date, odo: Double) {
            self.car = car
            self.odo_backend = odo
            self.timestamp = timestamp
        }
    }
    
  • 如果要求必须关联车辆,删除Picker中的--None--选项,确保selectedCar始终有值。

3. 优化保存与取消逻辑

  • 新建加油记录时,创建新对象而非修改临时对象,避免上下文冲突:
    Button("Done") {
        guard let selectedCar = selectedCar else {
            // 可选:提示用户选择车辆
            return
        }
        let newRefuel = Refuel(car: selectedCar, timestamp: refill.timestamp, odo: refill.odo_backend)
        modelContext.insert(newRefuel)
        dismiss()
    }
    
  • 取消时避免删除未插入上下文的临时对象:
    Button("Cancel") {
        if isNew {
            // 临时对象直接关闭
        } else {
            modelContext.delete(refill)
        }
        dismiss()
    }
    

4. 优化Car模型的关联关系

将refuels改为非可选空数组,简化nil处理:

@Model
final class Car: Identifiable {
    let id = UUID()
    var name: String
    @Relationship(deleteRule: .cascade, inverse: \Refuel.car) var refuels: [Refuel] = []
    
    init(name: String) {
        self.name = name
    }
}

内容的提问来源于stack exchange,提问作者f16

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 23:49:52