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

SwiftUI中模型变更无法在子视图同步显示的原因排查

SwiftUI视图不更新问题解决

问题代码

struct ContentView: View {
    
    @StateObject var cntrl=ContentViewController2()
    
    var body: some View {
        VStack {
            ItemsDisplay(items: cntrl.model.items, handler: cntrl.updateItem)
        }
    }
    
}

struct ItemsDisplay:View {
    
    let items:[SubItem]
    let handler:(SubItem)->Void
    
    init(items: [SubItem], handler: @escaping (SubItem) -> Void) {
        self.items = items
        self.handler = handler
        
        print("init of ItemsDisplay")
        for item in items {
            print("\(item.id) \(item.value)")
        }
    }
    
    var body: some View {
        VStack {
            ForEach(items, id: \.id) {item in
                ItemDisplay(item: item, handler: handler)
            }
        }
    }
}

struct ItemDisplay:View {
    
    let item:SubItem
    let handler:(SubItem)->Void
    
    init(item: SubItem, handler: @escaping (SubItem) -> Void) {
        self.item = item
        self.handler = handler
        print("init of ItemDisplay: \(item.value)")
    }
    
    var body: some View {
        VStack {
            Text("\(item.id)")
            Text("\(item.value)")
        }
        .padding(.all)
        .border(.black, width:5)
        .onTapGesture {
            print("tapped")
            handler(item)
        }
    }
}

struct SubItem:Equatable {
    
    static func == (lhs: SubItem, rhs: SubItem) -> Bool {
        lhs.id == rhs.id
    }
    
    let id:String
    var value:Int
    
    mutating func changeValue(_ newval:Int) {
        print("changing value to: \(newval)")
        value=newval
    }
}

struct ContentViewModel2 {
    var items:[SubItem]
    
    
    mutating func updateItem(_ item:SubItem) {
        if let i=items.firstIndex(of: item) {
            let newValue=item.value+1
            print("newValue for item: \(item.id) should be: \(newValue)")
            items[i].changeValue(newValue)
            print("newValue of item: \(items[i].id) is: \(items[i].value)")
        }
    }
    
}

class ContentViewController2:ObservableObject {
    @Published var model:ContentViewModel2
    
    init() {
        model=ContentViewModel2(items: [SubItem(id: "box1", value: 1), SubItem(id: "box10", value: 10)])
    }
    
    func updateItem(_ item:SubItem) {
        if let i=model.items.firstIndex(of: item) {
            print("updating: \(item.id)")
            model.updateItem(item)
        }
//        objectWillChange.send()
    }
}

问题描述

代码实现了包含两个SubItem盒子的界面,点击盒子应让对应数字加1。目前模型中的值已正确更新,但视图无法显示最新值。控制台输出显示ItemsDisplay的init方法已拿到更新后的items,但body方法未触发刷新。

点击box1时控制台输出:

tapped
updating: box1
newValue for item: box1 should be: 2
changing value to: 2
newValue of item: box1 is: 2
init of ItemsDisplay
box1 2
box10 10

问题原因

@Published仅在整个被观察对象实例替换或数组整体替换时才会发送更新通知。当前代码只是修改了数组中元素的属性,model本身的内存地址未发生变化,@Published不会触发视图刷新。

修复方案

方案一:替换整个数组

修改ContentViewModel2的updateItem方法,创建新数组替换原数组,让@Published检测到变化:

mutating func updateItem(_ item:SubItem) {
    if let i=items.firstIndex(of: item) {
        var newItems = items
        let newValue=item.value+1
        print("newValue for item: \(item.id) should be: \(newValue)")
        newItems[i].changeValue(newValue)
        items = newItems // 替换整个数组触发更新
        print("newValue of item: \(items[i].id) is: \(items[i].value)")
    }
}

方案二:手动发送更新通知

在ContentViewController2的updateItem方法中手动调用objectWillChange.send(),强制通知视图刷新:

func updateItem(_ item:SubItem) {
    if let i=model.items.firstIndex(of: item) {
        print("updating: \(item.id)")
        model.updateItem(item)
        objectWillChange.send() // 手动发送更新信号
    }
}

补充优化

让SubItem遵守Identifiable协议,简化ForEach写法:

struct SubItem:Equatable, Identifiable {
    let id:String
    var value:Int
    
    static func == (lhs: SubItem, rhs: SubItem) -> Bool {
        lhs.id == rhs.id
    }
    
    mutating func changeValue(_ newval:Int) {
        print("changing value to: \(newval)")
        value=newval
    }
}

此时ItemsDisplay中的ForEach可简化为:

ForEach(items) { item in
    ItemDisplay(item: item, handler: handler)
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 01:44:54