SwiftUI中实现openWindow数据绑定,同步InputView与ResultView
多窗口SwiftUI数据同步解决方案
问题分析
当前代码里,ResultView的Model是局部的@StateObject,打开InputWindow时传递的是model.calculation的值副本(因为Calculation是值类型),所以在InputView中修改input只会改动副本,无法同步回ResultView的原Model,导致视图无法实时更新。而弹窗(popover)能正常工作,是因为它共享同一个环境的状态,不存在值传递的副本问题。
要实现两个独立窗口的实时同步,核心是让两个窗口共享同一个数据源,避免值副本的隔离问题。
解决方案:全局共享ObservableObject
将Model提升到App级别作为全局状态,通过EnvironmentObject传递给所有需要的视图,同时让InputView直接绑定到全局Model的calculation属性,确保修改能实时同步到所有关联视图。
修改后的完整代码
import SwiftUI @main struct TestApp: App { @AppStorage("exponent") var exponent = 3 @AppStorage("expList") var expList = [3] // 全局共享的Model,用@StateObject管理生命周期 @StateObject private var model = Model(exponent: 3) var body: some Scene { WindowGroup { ResultView() .environmentObject(model) // 注入全局Model到环境 // 监听exponent变化,更新Model中的指数 .onChange(of: exponent) { newExp in model.updateExponent(to: Double(newExp)) } } // 直接打开InputWindow,传递全局Model的calculation绑定 WindowGroup("Input Window", id: "inputWindow") { InputView(calculation: $model.calculation) } .commands{ CommandMenu("Tools"){ Menu("Exponents"){ Picker("",selection: $exponent){ ForEach(expList, id:\.self) { exp in Text(String(exp)).tag(exp) } } .pickerStyle(.inline) .labelsHidden() Divider() Button("Add"){ expList = expList.sorted() let newExp = expList.last! + 1 expList.append(newExp) } } } } } } class Model: ObservableObject { @Published public var calculation: Calculation init(exponent: Double) { self.calculation = Calculation(exponent: exponent) } // 更新指数时保留当前输入值 func updateExponent(to newExp: Double) { calculation = Calculation(exponent: newExp, input: calculation.input) } } struct Calculation: Codable, Hashable { var exponent: Double var input: Double = 2 // 计算属性,input或exponent变化时自动更新结果 var result: Double { return pow(input, exponent) } init(exponent: Double) { self.exponent = exponent } // 新增初始化方法,切换指数时保留现有输入值 init(exponent: Double, input: Double) { self.exponent = exponent self.input = input } } struct ResultView: View { // 从环境中获取全局Model @EnvironmentObject private var model: Model @Environment(\.openWindow) private var openWindow var body: some View { VStack { Button("Set Input"){ openWindow(id:"inputWindow") } Text("Exponent: \(model.calculation.exponent)") Text("Input: \(model.calculation.input)") Text("Result: \(model.calculation.result)") } .frame(width: 200, height: 200) } } struct InputView: View { // 直接绑定全局Model的calculation属性 @Binding var calculation: Calculation var body: some View { HStack{ Text("Input: ") TextField("", value: $calculation.input, format: .number) } .padding() } } extension Array: RawRepresentable where Element: Codable { public init?(rawValue: String) { guard let data = rawValue.data(using: .utf8), let result = try? JSONDecoder().decode([Element].self, from: data) else { return nil } self = result } public var rawValue: String { guard let data = try? JSONEncoder().encode(self), let result = String(data: data, encoding: .utf8) else { return "[]" } return result } }
关键修改点
- 全局Model:在
TestApp中用@StateObject声明全局共享的Model,确保整个App生命周期内只有一个实例,避免状态分散。 - 环境注入:
ResultView通过@EnvironmentObject获取全局Model,不再局部创建实例,实现状态共享。 - 绑定传递:打开
InputWindow时直接传递$model.calculation的绑定,InputView修改的是全局Model中的属性,而非独立副本。 - 指数更新逻辑:新增
updateExponent方法,切换指数时保留当前输入值,提升用户操作连贯性。
扩展方案:复杂数据用ObservableObject
如果你的Calculation包含更多复杂属性,可以将它改为ObservableObject,这样内部属性变化时能更精准地触发视图更新,无需重新创建实例:
class Calculation: ObservableObject, Codable, Hashable { @Published var exponent: Double @Published var input: Double = 2 var result: Double { return pow(input, exponent) } init(exponent: Double) { self.exponent = exponent } // 实现Codable协议 enum CodingKeys: String, CodingKey { case exponent, input } required init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) exponent = try container.decode(Double.self, forKey: .exponent) input = try container.decodeIfPresent(Double.self, forKey: .input) ?? 2 } func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) try container.encode(exponent, forKey: .exponent) try container.encode(input, forKey: .input) } // 实现Hashable协议 static func == (lhs: Calculation, rhs: Calculation) -> Bool { lhs.exponent == rhs.exponent && lhs.input == rhs.input } func hash(into hasher: inout Hasher) { hasher.combine(exponent) hasher.combine(input) } } // 对应的Model更新 class Model: ObservableObject { @Published public var calculation: Calculation init(exponent: Double) { self.calculation = Calculation(exponent: exponent) } func updateExponent(to newExp: Double) { calculation.exponent = newExp } }
这种方式下,修改input时@Published会直接触发视图更新,更适合复杂状态的精细化管理。
内容的提问来源于stack exchange,提问作者G J
相关产品推荐
相关产品推荐

