SwiftUI+MVVM中如何在@Observable视图模型中使用绑定?
解决SwiftUI MVVM中多视图状态同步问题
你的问题核心在于子ViewModel接收的是主ViewModel属性的拷贝值,而非双向绑定的引用:
- 主视图点击按钮时,
MainViewModel.sourceToggle变化会触发MainView的body刷新,此时会重新初始化SubViewModel并传入新的sourceToggle值,所以子视图文本会更新; - 但子视图点击按钮时,修改的只是
SubViewModel自己的bindingToggle变量,和MainViewModel的数据源完全无关,因此主视图不会同步变化。
下面提供几种可行的解决方案:
方案1:通过Binding传递双向绑定关系
让子ViewModel持有主ViewModel属性的Binding,直接操作原数据源:
修改SubViewModel
@Observable class SubViewModel { let bindingToggle: Binding<Bool> init(bindingToggle: Binding<Bool>) { self.bindingToggle = bindingToggle } func toggle() { bindingToggle.wrappedValue.toggle() } }
修改MainView中SubView的初始化
struct MainView: View { var viewModel = MainViewModel() var body: some View { Text(viewModel.sourceToggle ? "isOn" : "isOff") Button("Toggle1") { viewModel.sourceToggle.toggle() } // 传递sourceToggle的Binding SubView(viewModel: SubViewModel(bindingToggle: $viewModel.sourceToggle)) } }
修改SubView的按钮操作
struct SubView: View { @Bindable var viewModel: SubViewModel var body: some View { Text(viewModel.bindingToggle.wrappedValue ? "isOn" : "isOff") Button("Toggle2") { viewModel.toggle() } } }
方案2:子ViewModel持有主ViewModel引用(更符合MVVM共享数据源思路)
让子ViewModel直接依赖主ViewModel,通过访问主ViewModel的属性实现状态同步:
修改SubViewModel
@Observable class SubViewModel { private let mainViewModel: MainViewModel // 通过计算属性代理主ViewModel的sourceToggle var bindingToggle: Bool { get { mainViewModel.sourceToggle } set { mainViewModel.sourceToggle = newValue } } init(mainViewModel: MainViewModel) { self.mainViewModel = mainViewModel } func toggle() { mainViewModel.sourceToggle.toggle() } }
修改MainView中SubView的初始化
struct MainView: View { var viewModel = MainViewModel() var body: some View { Text(viewModel.sourceToggle ? "isOn" : "isOff") Button("Toggle1") { viewModel.sourceToggle.toggle() } // 传入主ViewModel实例 SubView(viewModel: SubViewModel(mainViewModel: viewModel)) } }
SubView无需额外修改
struct SubView: View { @Bindable var viewModel: SubViewModel var body: some View { Text(viewModel.bindingToggle ? "isOn" : "isOff") Button("Toggle2") { viewModel.toggle() // 或者直接修改:viewModel.bindingToggle.toggle() } } }
方案3:简化实现——直接给子视图传递Binding(适合子视图逻辑简单的场景)
如果子视图不需要复杂的ViewModel逻辑,可以直接跳过SubViewModel,让子视图接收Binding:
修改SubView
struct SubView: View { @Binding var toggleValue: Bool var body: some View { Text(toggleValue ? "isOn" : "isOff") Button("Toggle2") { toggleValue.toggle() } } }
修改MainView中SubView的调用
struct MainView: View { var viewModel = MainViewModel() var body: some View { Text(viewModel.sourceToggle ? "isOn" : "isOff") Button("Toggle1") { viewModel.sourceToggle.toggle() } SubView(toggleValue: $viewModel.sourceToggle) } }
以上三种方案都能实现主、子视图的状态双向同步,你可以根据子视图的业务复杂度选择合适的方案。
内容的提问来源于stack exchange,提问作者Max
相关产品推荐
相关产品推荐

