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

SwiftUI中父子视图模型间传递数据的最优方案

父子视图模型间数据传递的最佳方案(不用EnvironmentObject)

针对你这种仅父子视图间传递数据的场景,以下是几种实用的实现方案,按需选择即可:

方案1:闭包回调(最推荐,轻量解耦)

核心思路是给子视图模型添加一个回调闭包,当子模型的选中数据变化时,主动把新值传递给父模型。

修改子视图模型:

class ChildViewModel: ObservableObject {
    @Published var selectedExercise = [String]()
    // 定义回调闭包,用于传递选中数据
    var onSelectionChanged: (([String]) -> Void)?
    
    init() {
        // 监听selectedExercise的变化,触发闭包
        $selectedExercise
            .sink { [weak self] newSelection in
                self?.onSelectionChanged?(newSelection)
            }
            .store(in: &cancellables)
    }
    
    private var cancellables = Set<AnyCancellable>()
}

父视图中初始化子模型并绑定回调:

struct ParentView: View {
    @StateObject private var parentVM = ParentViewModel()
    @StateObject private var childVM: ChildViewModel
    
    init() {
        let childVM = ChildViewModel()
        // 设置回调,更新父模型的数据
        childVM.onSelectionChanged = { newSelection in
            self.parentVM.selectedExercise = newSelection
        }
        self._childVM = StateObject(wrappedValue: childVM)
    }
    
    var body: some View {
        VStack {
            Text("父视图选中:\(parentVM.selectedExercise.joined(separator: ", "))")
            // 嵌入子视图,传入子模型
            ChildView(viewModel: childVM)
        }
    }
}

struct ChildView: View {
    @ObservedObject var viewModel: ChildViewModel
    
    var body: some View {
        // 示例交互:点击按钮添加选中项
        Button("添加练习") {
            viewModel.selectedExercise.append("练习\(Int.random(in: 1...10))")
        }
    }
}

优点:子模型无需知道父模型的具体类型,解耦性强;实现简单,完全适配仅父子间传递的场景。

方案2:子模型持有父模型的弱引用

让子模型直接持有父模型的弱引用,当自身数据变化时,主动更新父模型的对应属性。

修改子视图模型:

class ChildViewModel: ObservableObject {
    @Published var selectedExercise = [String]() {
        didSet {
            // 数据变化时同步到父模型
            parentVM?.selectedExercise = selectedExercise
        }
    }
    // 弱引用父模型,避免循环引用
    weak var parentVM: ParentViewModel?
    
    init(parentVM: ParentViewModel?) {
        self.parentVM = parentVM
    }
}

父视图中初始化子模型:

struct ParentView: View {
    @StateObject private var parentVM = ParentViewModel()
    @StateObject private var childVM: ChildViewModel
    
    init() {
        self._childVM = StateObject(wrappedValue: ChildViewModel(parentVM: parentVM))
    }
    
    var body: some View {
        VStack {
            Text("父视图选中:\(parentVM.selectedExercise.joined(separator: ", "))")
            ChildView(viewModel: childVM)
        }
    }
}

优点:代码更简洁,无需额外的Combine订阅;缺点:子模型和父模型耦合度高,灵活性稍差。

方案3:父模型订阅子模型的Published属性

利用Combine框架,让父模型订阅子模型的selectedExercise变化,自动同步自身数据。

修改父视图模型:

class ParentViewModel: ObservableObject {
    @Published var selectedExercise = [String]()
    let childVM: ChildViewModel
    private var cancellables = Set<AnyCancellable>()
    
    init() {
        self.childVM = ChildViewModel()
        // 订阅子模型的选中数据变化,同步到自身
        childVM.$selectedExercise
            .assign(to: &$selectedExercise)
    }
}

父视图中直接使用父模型持有的子模型:

struct ParentView: View {
    @StateObject private var parentVM = ParentViewModel()
    
    var body: some View {
        VStack {
            Text("父视图选中:\(parentVM.selectedExercise.joined(separator: ", "))")
            ChildView(viewModel: parentVM.childVM)
        }
    }
}

优点:父模型主动管理数据同步,逻辑集中;缺点:父模型需要持有子模型实例,耦合度比闭包方案高。

总结推荐

如果追求低耦合、高灵活性,优先选闭包回调方案;如果场景简单、想精简代码,可选弱引用父模型方案;Combine订阅适合需要更复杂数据流处理的场景。

内容的提问来源于stack exchange,提问作者Shin Shan

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 07:30:00