SwiftUI中不同ViewModel间的通信实现方案咨询
解决方案
当然可以实现两个ViewModel之间的通信,下面是几种实用的方案:
方案1:让ViewModel_2持有ViewModel_1的引用
这是最直接的实现方式,在父视图初始化时将ViewModel_1的实例传入ViewModel_2,让后者能直接调用前者的方法修改数据。
修改后的代码示例:
// 更新ViewModel_2,添加对ViewModel_1的引用 class ViewModel_2: ObservableObject { private let viewModel1: ViewModel_1 init(viewModel1: ViewModel_1) { self.viewModel1 = viewModel1 } func changeValue_in_ViewModel_1(value: Int) { viewModel1.changeValue(value: value) } } // 更新ParentView的初始化逻辑 struct ParentView: View { @StateObject var viewModel_1: ViewModel_1 @StateObject var viewModel_2: ViewModel_2 // 便捷初始化方法,关联两个ViewModel init() { let vm1 = ViewModel_1() _viewModel_1 = StateObject(wrappedValue: vm1) _viewModel_2 = StateObject(wrappedValue: ViewModel_2(viewModel1: vm1)) } var body: some View { ZStack { ChildView_1() .environmentObject(viewModel_1) ChildView_2() .environmentObject(viewModel_2) } } }
方案2:用Combine实现解耦式通信
如果希望两个ViewModel保持低耦合,可以利用Combine的发布订阅机制传递修改信号:
import Combine // 给ViewModel_2添加事件发布者 class ViewModel_2: ObservableObject { let valueChangeRequest = PassthroughSubject<Int, Never>() } // 让ViewModel_1订阅ViewModel_2的事件 class ViewModel_1: ObservableObject { @Published var someValue: Int = 0 private var cancellables = Set<AnyCancellable>() func subscribeToValueChanges(from viewModel2: ViewModel_2) { viewModel2.valueChangeRequest .sink { [weak self] newValue in self?.changeValue(value: newValue) } .store(in: &cancellables) } func changeValue(value: Int) { self.someValue = value } } // 在父视图中建立订阅关系 struct ParentView: View { @StateObject var viewModel_1: ViewModel_1 @StateObject var viewModel_2: ViewModel_2 init() { let vm1 = ViewModel_1() let vm2 = ViewModel_2() vm1.subscribeToValueChanges(from: vm2) _viewModel_1 = StateObject(wrappedValue: vm1) _viewModel_2 = StateObject(wrappedValue: vm2) } var body: some View { ZStack { ChildView_1() .environmentObject(viewModel_1) ChildView_2() .environmentObject(viewModel_2) } } } // 修改ChildView_2的按钮动作,触发事件发布 struct ChildView_2: View { @EnvironmentObject var viewModel_2: ViewModel_2 var body: some View { Button(action: { viewModel_2.valueChangeRequest.send(1) }, label: { Text("Tap to change value") }) } }
方案3:通过父视图中转通信
如果需要完全隔离两个ViewModel,可以把通信逻辑放在父视图层,让子视图通过闭包通知父视图,再由父视图操作目标ViewModel:
// 修改ChildView_2,添加回调闭包 struct ChildView_2: View { var onValueChange: (Int) -> Void var body: some View { Button(action: { onValueChange(1) }, label: { Text("Tap to change value") }) } } // 在ParentView中传递回调,操作ViewModel_1 struct ParentView: View { @StateObject var viewModel_1: ViewModel_1 = ViewModel_1() @StateObject var viewModel_2: ViewModel_2 = ViewModel_2() var body: some View { ZStack { ChildView_1() .environmentObject(viewModel_1) ChildView_2(onValueChange: { newValue in viewModel_1.changeValue(value: newValue) }) } } }
三种方案的适用场景:
- 方案1适合简单业务场景,逻辑直接但耦合度略高
- 方案2适合复杂项目,遵循响应式编程风格,实现低耦合
- 方案3适合需要严格隔离ViewModel的场景,将通信逻辑收拢到视图层
内容的提问来源于stack exchange,提问作者Simon Henn
相关产品推荐
相关产品推荐

