SwiftUI 父视图更新后 ViewModel 状态归零:检查对象由谁创建
父页面变化后输入或计数归零,检查模型创建位置、StateObject 所有权、视图身份及 iOS 17 Observation 的适用边界。
子页面的计数、输入内容或加载结果本来正常,一修改父页面的状态,子页面却回到初始值。若子视图里写着 @ObservedObject var model = EditorModel(),先检查模型是否随视图值重新构造,而不要立即把问题归为 @Published 没有生效。
本文适用于使用 ObservableObject 和 @Published 的 SwiftUI 项目。修复示例要求 iOS 14 或更高版本,因为用到了 @StateObject。iOS 17 引入的 Observation 模型使用另一套组合,不能把所有模型都统一替换成 @StateObject。
区分视图重新计算和模型被替换
下面是容易出问题的声明:
import SwiftUI
import Combine
final class EditorModel: ObservableObject {
@Published var text = ""
}
struct EditorView: View {
@ObservedObject var model = EditorModel()
var body: some View {
TextField("输入内容", text: $model.text)
}
}
@ObservedObject 负责订阅对象的变化,不负责为当前视图身份保存一个由它创建的模型。父视图重新生成 EditorView() 值时,这个默认值表达式可能创建另一个 EditorModel;新的对象当然仍有空字符串。
body 再次执行本身不意味着所有状态都会丢失。排查时,在模型的 init、deinit 中临时记录 ObjectIdentifier(self),并观察父页面一次无关更新前后正在使用的模型标识。若只是视图初始化次数增加,模型标识没变,就继续查赋值、数据刷新或身份变化,而不要仅凭 View.init 日志判断状态已经重置。
视图拥有模型时,用 StateObject 保存它
如果编辑器负责创建和持有自己的模型,把拥有者改成下面的写法,父页面保持正常的结构即可:
struct EditorView: View {
@StateObject private var model = EditorModel()
var body: some View {
TextField("输入内容", text: $model.text)
}
}
SwiftUI 在同一个视图身份的生命周期内保留这个对象。父视图输入变化、body 重新计算时,不需要为此手工缓存模型,也不需要关闭界面更新。
如果模型由上层统一管理,则在上层声明 @StateObject,子视图保留 @ObservedObject,但去掉默认构造:
struct DocumentScreen: View {
@StateObject private var model = EditorModel()
@State private var showHelp = false
var body: some View {
VStack {
Toggle("显示帮助", isOn: $showHelp)
SharedEditorView(model: model)
}
}
}
struct SharedEditorView: View {
@ObservedObject var model: EditorModel
var body: some View {
TextField("输入内容", text: $model.text)
}
}
这里切换帮助开关会更新父视图,但子视图仍观察上层持有的同一个模型。不要再在调用处写 SharedEditorView(model: EditorModel()),否则对象仍可能由父视图反复创建。
换成 StateObject 后仍重置,要查视图身份
@StateObject 不跨越所有页面生命周期永久保存数据。条件分支移除了编辑器、列表中对应元素换了 ID,或上层用了不断变化的 .id(...),都可能让 SwiftUI 把它当成新的视图。
尤其不要为了“强制刷新”写 .id(UUID())。每次重新求值都生成新身份,会连同局部 @State、焦点等状态一起重建。若用户离开页面后仍要保留草稿,把拥有者提升到确实存活更久的页面或应用模型,并按产品要求持久化;仅换一个属性包装器不能保存进程退出后的数据。
还有另一种容易误判的现象:通过 StateObject(wrappedValue:) 在视图初始化器中传入文档 ID 后,父页面换了 ID,模型却仍处理旧文档。对于同一视图身份,初始化输入不会自动重新创建模型。应明确选择“更新现有模型的输入”还是“切换文档时建立新身份”,而不是假定每次 View.init 都会更新模型。
Observation 项目和验收方式
如果模型声明为 @Observable,SwiftUI 可直接跟踪 body 读取的属性;视图拥有模型时通常使用 @State,需要属性绑定时再使用 @Bindable。@ObservedObject、@StateObject 要求的是 ObservableObject,不应套在仅采用 @Observable 的类型上。
修复后保留一次输入,触发父页面的无关更新,确认输入和模型标识都保持;再按产品设计切换文档、移除页面并重新进入。前一种操作不应意外清空状态,后一种是否重置则由模型的实际拥有者和视图身份决定。这样才能分清修复了生命周期问题,还是只是把状态移到了更大的作用域。


