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

如何在两个ViewModel间同步@Published属性用于SwiftUI视图绑定

解决两个ViewModel的@Published属性双向同步问题

你遇到的错误是因为直接将Binding<String?>类型的$vm.myProperty传给了期望String?类型参数的SecondViewModel初始化方法,同时当前代码无法实现两个ViewModel属性的双向同步。

核心解决方案

让SecondViewModel通过接收外部Binding实现与外部数据的双向绑定,无需知晓FirstViewModel的存在;利用Combine框架在SecondViewModel内部建立自身@Published属性与外部Binding的双向订阅,确保两者值始终一致。

修改后的完整代码

import SwiftUI
import Combine

class FirstViewModel: ObservableObject {
    @Published var myProperty: String?
    private var cancellables = Set<AnyCancellable>()

    init() {
        self.myProperty = "Two"
    }
}

struct FirstView: View {
    @EnvironmentObject var vm: FirstViewModel

    var body: some View {
        SecondView()
            .environmentObject(SecondViewModel(externalProperty: $vm.myProperty))
    }
}

class SecondViewModel: ObservableObject {
    @Published var myProperty: String?
    private var externalProperty: Binding<String?>
    private var cancellables = Set<AnyCancellable>()

    // 接收外部Binding,而非具体值
    init(externalProperty: Binding<String?>) {
        self.externalProperty = externalProperty
        
        // 1. 自身myProperty变化时,同步更新外部Binding
        $myProperty
            .sink { [weak self] newValue in
                guard let self = self else { return }
                // 避免循环触发,仅值不同时更新
                if newValue != self.externalProperty.wrappedValue {
                    self.externalProperty.wrappedValue = newValue
                }
            }
            .store(in: &cancellables)
        
        // 2. 外部Binding值变化时,同步更新自身myProperty
        externalProperty
            .projectedValue
            .sink { [weak self] newValue in
                guard let self = self else { return }
                if newValue != self.myProperty {
                    self.myProperty = newValue
                }
            }
            .store(in: &cancellables)
        
        // 初始化时同步外部值到自身
        self.myProperty = externalProperty.wrappedValue
    }
}

struct SecondView: View {
    @EnvironmentObject var vm: SecondViewModel
    let items: [String] = ["One", "Two", "Three"]

    var body: some View {
        Picker(
            selection: $vm.myProperty,
            label: EmptyView()
        ) {
            ForEach(items, id: \.self) { item in
                Text(item).tag(item as String?)
            }
        }
    }
}

关键修改说明

  • SecondViewModel初始化:改为接收Binding<String?>类型参数,彻底解耦与FirstViewModel的依赖
  • 双向订阅逻辑:
    • 订阅自身$myProperty的变化,同步更新外部Binding的值,加入值判断避免循环触发
    • 订阅外部Binding的projectedValue(Binding的发布者),同步更新自身myProperty
  • SecondView的Picker:给tag添加as String?类型标注,因为myProperty是可选类型,确保Picker选择类型匹配

内容的提问来源于stack exchange,提问作者Serhii R

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 06:25:31