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

SwiftUI视图中Stepper值更新后数组重计算滞后问题排查

解决SwiftUI Stepper更新食材数量滞后的问题

问题根源

你的IngredientClass是引用类型(class),当修改数组中元素的quantity属性时,@Published var ingredients: [IngredientClass]数组本身的内存引用并未发生改变。SwiftUI的ObservableObject仅会在数组被重新赋值、元素增删/替换时触发视图更新,因此修改元素内部属性无法即时通知视图刷新,导致显示滞后一步。

解决方案

方案1:将IngredientClass改为值类型(struct)

这是SwiftUI推荐的实现方式,值类型的属性修改会自动触发数组的变化通知:

struct IngredientClass: Codable, Identifiable { // 将class改为struct
    var id: Int?
    var version: Int?
    var familyId: Int
    var recipeId: Int?
    var indexOrder: Int?
    var quantity: Double
    var originalQuantity: Double?
    var unit: String?
    var name: String
    var updated: String?
    var deleted: Bool
}

修改后,ViewModel中的recalcQuantity方法无需额外改动——修改struct的quantity会生成新的实例,数组元素变化会被@Published检测到,触发视图即时更新。

方案2:保留class但手动触发更新

如果必须使用class,可通过以下两种方式手动通知视图更新:

方式A:给IngredientClass的quantity添加@Published

让IngredientClass本身成为ObservableObject,属性变化时自动通知:

class IngredientClass: Codable, Identifiable, ObservableObject {
    var id: Int?
    var version: Int?
    var familyId: Int
    var recipeId: Int?
    var indexOrder: Int?
    @Published var quantity: Double // 给quantity添加@Published
    var originalQuantity: Double?
    var unit: String?
    var name: String
    var updated: String?
    var deleted: Bool
    
    // 手动实现Codable协议(因继承ObservableObject)
    enum CodingKeys: String, CodingKey {
        case id, version, familyId, recipeId, indexOrder, quantity, originalQuantity, unit, name, updated, deleted
    }
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        id = try container.decodeIfPresent(Int.self, forKey: .id)
        version = try container.decodeIfPresent(Int.self, forKey: .version)
        familyId = try container.decode(Int.self, forKey: .familyId)
        recipeId = try container.decodeIfPresent(Int.self, forKey: .recipeId)
        indexOrder = try container.decodeIfPresent(Int.self, forKey: .indexOrder)
        quantity = try container.decode(Double.self, forKey: .quantity)
        originalQuantity = try container.decodeIfPresent(Double.self, forKey: .originalQuantity)
        unit = try container.decodeIfPresent(String.self, forKey: .unit)
        name = try container.decode(String.self, forKey: .name)
        updated = try container.decodeIfPresent(String.self, forKey: .updated)
        deleted = try container.decode(Bool.self, forKey: .deleted)
    }
    
    func encode(to encoder: Encoder) throws {
        var container = encoder.container(keyedBy: CodingKeys.self)
        try container.encodeIfPresent(id, forKey: .id)
        try container.encodeIfPresent(version, forKey: .version)
        try container.encode(familyId, forKey: .familyId)
        try container.encodeIfPresent(recipeId, forKey: .recipeId)
        try container.encodeIfPresent(indexOrder, forKey: .indexOrder)
        try container.encode(quantity, forKey: .quantity)
        try container.encodeIfPresent(originalQuantity, forKey: .originalQuantity)
        try container.encodeIfPresent(unit, forKey: .unit)
        try container.encode(name, forKey: .name)
        try container.encodeIfPresent(updated, forKey: .updated)
        try container.encode(deleted, forKey: .deleted)
    }
    
    // 自定义初始化器,方便创建实例
    init(id: Int?, version: Int?, familyId: Int, recipeId: Int?, indexOrder: Int?, quantity: Double, originalQuantity: Double?, unit: String?, name: String, updated: String?, deleted: Bool) {
        self.id = id
        self.version = version
        self.familyId = familyId
        self.recipeId = recipeId
        self.indexOrder = indexOrder
        self.quantity = quantity
        self.originalQuantity = originalQuantity
        self.unit = unit
        self.name = name
        self.updated = updated
        self.deleted = deleted
    }
}

视图中显示食材时,需要用ObservedObject包裹:

ForEach(vm.ingredients) { ingredient in
    ObservedObject(wrappedValue: ingredient) { $ingr in
        Text("\(ingr.quantity) \(ingr.unit ?? "") \(ingr.name)")
    }
}

方式B:在ViewModel中手动发送更新通知

修改完所有元素的quantity后,调用objectWillChange.send()强制通知视图更新:

class RecipeViewModel: ObservableObject {

    @Published var ingredients: [IngredientClass] = []
    
    func recalcQuantity(serves: Int, servesOrig: Int) {
        ingredients.indices.forEach {
            ingredients[$0].quantity = ((ingredients[$0].originalQuantity ?? 1.0) / Double(servesOrig)) * Double(serves)
        }
        objectWillChange.send() // 手动触发视图更新
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.21 03:55:34