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

SwiftUI调用objectWillChange.send()未更新子视图问题排查

问题

我构建了一套嵌套层级复杂的SwiftUI视图。触发按钮动作时,我通过viewModel类传递一个可选闭包,该闭包会调用viewModel的objectWillChange.send(),且已确认该方法被触发(视图其他部分已正常更新)。但其中一个监听viewModel变化的子视图PurchaseItemGrid,直到点击它的某个部分(该操作会修改viewModel.selectedIndex并触发重绘,说明它确实在监听发布的变化)才会更新。

为何此次更新无法触发PurchaseItemGrid子视图重绘?


相关代码

RightSideView 代码

struct RightSideView: View {
    @ObservedObject var viewModel: TrenchesPurchases
            
    var body: some View {
        VStack {
            ...
            PurchaseItemGrid(viewModel: viewModel)      // <-- 未更新的视图
            Button {
                viewModel.purchaseAction() {
                    viewModel.objectWillChange.send()   // <-- 触发重绘,断点已确认执行
                }
            } label: {
                ...
            }
            ...
        }
    }
}

ViewModel 代码

class TrenchesPurchases: ObservableObject, CanPushCurrency {

    // MARK: - 属性

    @Published private var model = Purchases()

    // MARK: - 计算属性

    var selectedIndex: Int {
        get { return model.selectedIndex }
        set { model.selectedIndex = newValue }
    }

    var purchaseAction: BlockWithBlock {
        { complete in                    
            ...
            
            complete?()
        }
    }
    ...
}

PurchaseItemGrid 代码

struct PurchaseItemGrid: View {

   @ObservedObject var viewModel: TrenchesPurchases
    
   var body: some View {
        VStack {
            itemRow(indices:  0...3)
            ...
        }
        ...
    }
    
    @ViewBuilder
    func itemRow(indices range: ClosedRange<Int>) -> some View {
        HStack {
            ForEach(viewModel.purchaseItems[range], id: \.id) { item in
                PurchaseItemView(item: item,
                                 borderColor: viewModel.selectedIndex == item.id ? .green : Color(Colors.oliveGreen))
                .onTapGesture { viewModel.selectedIndex = item.id }
            }
        }
    }
}

Purchases 模型代码

struct Purchases {

    // MARK: - 属性

    var selectedIndex = 15

    let items: [PurchaseItem] = buildCollectionOfItems()

    // MARK: - 函数

    // MARK: - 静态函数

    // TODO: 添加注释
    static func buildCollectionOfItems() -> [PurchaseItem] {
        return row0() + row1() + row2() + row3()
}

static func row0() -> [PurchaseItem] {
    var items = [PurchaseItem]()
    
    let grenade = Ammo(ammo: .grenade)
    items.append(grenade)
    
        let bullets = Ammo(ammo: .bullets)
        items.append(bullets)
    
        let infiniteBullets = Unlock(mode: .defense)
        items.append(infiniteBullets)
    
        let unlimitedInfantry = Unlock(mode: .offense)
        items.append(unlimitedInfantry)
    
        return items
    }

    static func row1() -> [PurchaseItem] {
        var items = [PurchaseItem]()
    
        for unit in UnitType.allCases {
            let item = Unit(unit: unit)
            items.append(item)
        }
        
        return items
    }

    static func row2() -> [PurchaseItem] {
        var items = [PurchaseItem]()

        let brits = NationItem(nation: .brits)
        items.append(brits)
    
        let turks = NationItem(nation: .turks)
        items.append(turks)
    
        let usa = NationItem(nation: .usa)
        items.append(usa)
    
        let insane = DifficultyItem(difficulty: .insane)
        items.append(insane)
    
        return items
    }

    static func row3() -> [PurchaseItem] {
        var items = [PurchaseItem]()

        let offenseLootBox = Random(mode: .offense)
        items.append(offenseLootBox)
    
        let defenseLootBox = Random(mode: .defense)
        items.append(defenseLootBox)
    
        let currency = Currency(isCheckin: false)
        items.append(currency)
    
        let checkIn = Currency(isCheckin: true)
        items.append(checkIn)
    
        return items
    }
}

原因分析与解决方案

核心原因

SwiftUI的视图更新逻辑是基于依赖属性的实际变化,而非单纯的objectWillChange通知。PurchaseItemGrid的body仅依赖viewModel.purchaseItems和viewModel.selectedIndex两个属性,你手动调用objectWillChange.send()时,这两个属性的值并未发生任何改变——因此SwiftUI判定该视图无需重绘,直接跳过了更新流程。

而点击子视图时修改selectedIndex,该属性绑定到@Published修饰的model,修改会自动触发精准的属性变更通知,SwiftUI能检测到依赖属性的变化,所以视图会正常更新。

解决方案

方案1:更新实际依赖的属性(推荐)

放弃手动调用objectWillChange.send(),转而直接修改视图依赖的属性。如果purchaseAction逻辑中需要更新商品状态,确保通过@Published修饰的model完成修改:

// 在TrenchesPurchases中添加更新商品的方法
func updatePurchaseItem(at index: Int, newState: PurchaseItemState) {
    // 假设model.items改为var类型
    model.items[index].state = newState
    // model是@Published,修改后自动触发objectWillChange通知
}

方案2:强制触发视图更新(临时应急用)

如果确实需要手动触发更新且不想修改属性,可以通过修改视图的id强制重绘:

// 在RightSideView中添加状态
@State private var refreshGridID = UUID()

// 按钮动作中更新ID
Button {
    viewModel.purchaseAction() {
        refreshGridID = UUID()
    }
} label: { ... }

// 给PurchaseItemGrid绑定ID
PurchaseItemGrid(viewModel: viewModel)
    .id(refreshGridID)

方案3:修正属性绑定逻辑

将purchaseItems改为绑定到model.items的计算属性,确保model的变更能自动传递给视图:

// 在TrenchesPurchases中添加
var purchaseItems: [PurchaseItem] {
    get { model.items }
    set { model.items = newValue }
}

这样当model.items变化时,@Published会自动触发通知,SwiftUI能检测到purchaseItems的变化,从而更新PurchaseItemGrid。


内容的提问来源于Stack Exchange,提问作者Mercutio

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 15:45:41