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

编辑子视图后UI未更新的SwiftUI技术问题

问题描述

我的数据模型是包含对象数组的类:

class InnerDataItem : Identifiable, ObservableObject {
    var id = UUID()
    @Published var innerInfo = ""
    
    init(innerInfo: String = "") {
        self.innerInfo = innerInfo
    }
}

class MyDataModel : Identifiable, ObservableObject {
    let id: UUID = UUID()
    @Published var info = "My String array"
    @Published var innerData: [InnerDataItem] = []

    init(info: String = "My String array", innerData: [InnerDataItem]) {
        self.info = info
        self.innerData = innerData
    }
}

定义了该模型的全局实例:

let modelData = MyDataModel(
    info: "Data model object",
    innerData: [
        InnerDataItem(innerInfo: "Inner info One"),
        InnerDataItem(innerInfo: "Inner info Two"),
        InnerDataItem(innerInfo: "Inner info Three"),
    ])

ContentView通过App传入模型,展示innerData列表:

@main
struct TestApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView(dataModel: modelData)
        }
    }
}

struct ContentView: View {
    @ObservedObject var dataModel: MyDataModel
    
    var body: some View {
        NavigationView {
            List {
                ForEach(dataModel.innerData) { innerItem in
                    NavigationLink {
                        InnerDataItemEditView(data: innerItem)
                    } label: {
                        Text(innerItem.innerInfo)
                    }
                }.onMove(perform: { indices, newOffset in
                    dataModel.move(fromOffsets: indices, toOffset: newOffset)
                })
            }
            .navigationBarItems(trailing: EditButton())
        }
    }
}

InnerDataItemEditView用TextField修改子项的值:

struct InnerDataItemEditView: View {
    @ObservedObject var data: InnerDataItem
    
    var body: some View {
        TextField("Info: ", text: $data.innerInfo)
    }
}

问题:编辑InnerDataItem的值时,数据已更新,但ContentView的List不会同步刷新;只有用Edit按钮重排序后,修改的值才会显示。尝试传递Binding到InnerDataItemEditView但无法编译,请问UI无法正常更新的原因是什么?


原因与解决方案

核心原因

ContentView的List依赖dataModel.innerData的@Published通知触发刷新,但innerData是ObservableObject对象数组。当数组内单个InnerDataItem的内部@Published属性更新时,MyDataModel的innerData数组本身的引用没有变化,因此dataModel不会发送objectWillChange通知,ContentView也就不会刷新。而重排序操作修改了数组的元素顺序,触发了innerData的@Published通知,才会让List刷新并显示最新值。

解决方案

有两种可行的修复方式:

方式一:将InnerDataItem改为值类型(struct)

把InnerDataItem从class改成struct,去掉ObservableObject协议(值类型的属性变化会直接触发数组的变化,进而触发MyDataModel的@Published通知):

struct InnerDataItem : Identifiable {
    var id = UUID()
    var innerInfo = ""
    
    init(innerInfo: String = "") {
        self.innerInfo = innerInfo
    }
}

同时修改InnerDataItemEditView为接收Binding:

struct InnerDataItemEditView: View {
    @Binding var data: InnerDataItem
    
    var body: some View {
        TextField("Info: ", text: $data.innerInfo)
    }
}

在ContentView的ForEach中传递Binding:

ForEach($dataModel.innerData) { $innerItem in
    NavigationLink {
        InnerDataItemEditView(data: $innerItem)
    } label: {
        Text(innerItem.innerInfo)
    }
}

这种方式更符合SwiftUI的设计理念,值类型的状态管理更简单直观。

方式二:监听子项变化并通知父模型

如果必须保留InnerDataItem为类类型,需要在MyDataModel中监听每个InnerDataItem的objectWillChange事件,当子项变化时手动触发自身的objectWillChange通知:

class MyDataModel : Identifiable, ObservableObject {
    let id: UUID = UUID()
    @Published var info = "My String array"
    @Published var innerData: [InnerDataItem] = [] {
        willSet {
            // 移除旧子项的监听
            innerData.forEach { stopListening(to: $0) }
            // 添加新子项的监听
            newValue.forEach { startListening(to: $0) }
        }
    }
    
    private var cancellables: [UUID: AnyCancellable] = [:]
    
    init(info: String = "My String array", innerData: [InnerDataItem]) {
        self.info = info
        self.innerData = innerData
        // 初始化时添加监听
        innerData.forEach { startListening(to: $0) }
    }
    
    private func startListening(to item: InnerDataItem) {
        let cancellable = item.objectWillChange.sink { [weak self] _ in
            self?.objectWillChange.send()
        }
        cancellables[item.id] = cancellable
    }
    
    private func stopListening(to item: InnerDataItem) {
        cancellables.removeValue(forKey: item.id)
    }
    
    // 实现move方法
    func move(fromOffsets indices: IndexSet, toOffset newOffset: Int) {
        innerData.move(fromOffsets: indices, toOffset: newOffset)
    }
}

这样,当任何一个InnerDataItem的属性变化时,MyDataModel会主动发送通知,ContentView的List就会同步刷新了。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.27 10:31:29