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

