SwiftUI中如何将子视图ViewModel的数据传递给父视图ViewModel?
SwiftUI 嵌套视图跨ViewModel传值实现方案
核心注意点:不要在ViewModel2中直接强持有ViewModel1的实例,否则会造成代码高度耦合、循环引用问题,所有传值逻辑要符合SwiftUI单向数据流设计规则
以下是3种生产环境常用的实现方案,可根据业务场景选择:
方案1:闭包回调(耦合度最低,适合绝大多数简单传值场景)
实现逻辑:初始化子视图、注入ViewModel2时,提前给ViewModel2定义好数据回调闭包,当ViewModel2中需要同步的数据发生变更、或者触发指定操作时,调用闭包把数据抛给上层的ViewModel1即可。
代码示例:import SwiftUI import Combine // 父视图ViewModel class ViewModel1: ObservableObject { @Published var receivedChildData: String = "" } // 子视图ViewModel class ViewModel2: ObservableObject { @Published var inputContent: String = "" // 定义传值回调,参数为需要传递的数据类型 var dataSendHandler: ((String) -> Void)? func triggerSendData() { // 传值触发时机可自行控制:比如数据更新、按钮点击、网络请求回调等 dataSendHandler?(inputContent) } } // 父视图 struct View1: View { @StateObject private var vm1 = ViewModel1() var body: some View { VStack(spacing: 20) { Text("父VM收到的数据:\(vm1.receivedChildData)") .font(.headline) // 注入子视图实例时提前配置回调 View2(vm2: { let vm2 = ViewModel2() // 用弱引用避免循环引用 vm2.dataSendHandler = { [weak vm1] data in vm1?.receivedChildData = data } return vm2 }()) } .padding() } } // 子视图 struct View2: View { @ObservedObject var vm2: ViewModel2 var body: some View { VStack { TextField("输入要传递的内容", text: $vm2.inputContent) .textFieldStyle(.roundedBorder) Button("点击传递数据给父VM") { vm2.triggerSendData() } .buttonStyle(.borderedProminent) } .padding() } }方案2:Combine事件发布(适合传值链路长、存在多个数据订阅方的场景)
实现逻辑:在ViewModel2中定义数据事件发布者,ViewModel1初始化后主动订阅这个发布者,即可实时接收ViewModel2推送的数据,注意要把订阅关系存储到
AnyCancellable集合中,避免提前释放。
核心代码示例:class ViewModel2: ObservableObject { @Published var inputContent: String = "" // 定义数据发布者 let dataPublisher = PassthroughSubject<String, Never>() func triggerSendData() { dataPublisher.send(inputContent) } } class ViewModel1: ObservableObject { @Published var receivedChildData: String = "" private var cancellableSet = Set<AnyCancellable>() // 提供绑定子VM发布者的方法 func configChildVMBinding(vm2: ViewModel2) { vm2.dataPublisher .receive(on: DispatchQueue.main) .assign(to: \.receivedChildData, on: self) .store(in: &cancellableSet) } }父视图使用时,在视图加载完成的生命周期里调用
vm1.configChildVMBinding(vm2: vm2)完成绑定即可。方案3:父VM托管子VM实例(适合两个视图业务强关联的场景)
实现逻辑:如果View1和View2的业务逻辑强绑定,不需要做模块拆分,可以直接把ViewModel2作为ViewModel1的属性持有,父视图初始化时直接把ViewModel1内部的ViewModel2实例传给View2,ViewModel1可以直接订阅ViewModel2的属性变化,不需要额外做传值中转。
注意:使用该方案时,子视图绝对不能自己重新初始化ViewModel2实例,必须统一使用父ViewModel持有的实例,否则会出现状态不同步的问题。
内容的提问来源于stack exchange,提问作者Tatiana Cooper
相关产品推荐
相关产品推荐

