如何在Swift中声明可跨多SwiftUI视图(窗口)共享的变量?
问题分析
你的核心问题是**@Binding仅适用于父子视图层级的双向绑定**,而通过URL Scheme打开的新窗口属于独立的视图上下文,无法直接通过@Binding共享状态。要实现跨窗口的变量同步(支持Int和String类型),需要采用全局状态管理方案,同时可以结合URL参数传递初始值。
解决方案
我们通过@EnvironmentObject(SwiftUI 3及以下)或@Observable(SwiftUI 4+)创建全局共享的数据模型,让所有窗口都能访问并同步同一个数据源。
1. 创建全局共享数据模型
这个模型会存储需要跨窗口同步的Int和String值,并用@Published(或SwiftUI 4+的自动发布)确保视图能响应数据变化:
import SwiftUI import Combine // SwiftUI 4+ 可替换为 @Observable 注解,无需继承ObservableObject @MainActor class SharedData: ObservableObject { @Published var numberValue: Int = 0 @Published var stringValue: String = "" }
2. 在App入口注入全局模型
将共享模型注入App的环境中,让所有窗口都能访问:
@main struct MyApp: App { // SwiftUI 3及以下用@StateObject,SwiftUI 4+用@State @StateObject private var sharedData = SharedData() var body: some Scene { WindowGroup { ContentView() .environmentObject(sharedData) } // 为DetailView注册独立窗口组,响应URL Scheme WindowGroup("DetailView") { DetailView() .environmentObject(sharedData) } .handlesExternalEvents(matching: ["DetailView"]) } }
3. 修改ContentView,使用全局状态
替换原来的@State为环境对象,同时添加String类型的输入框:
import SwiftUI struct ContentView : View { @Environment(\.presentationMode) private var presentationMode @EnvironmentObject private var sharedData: SharedData var body: some View { VStack(spacing: 20) { Text("Testing this") .navigationTitle("The program") // 绑定全局模型的Int值 TextField("Enter number", value: $sharedData.numberValue, formatter: NumberFormatter()) .frame(width: 150.0, height: 20.0) // 绑定全局模型的String值 TextField("Enter text", text: $sharedData.stringValue) .frame(width: 150.0, height: 20.0) Button(action: { // 可选:传递当前值作为URL参数,确保打开窗口时同步初始值 OpenWindows.DetailView.open(with: sharedData) presentationMode.wrappedValue.dismiss() }){ Text("Open Detail Window") } } .frame(width: 400.0, height: 200.0) } } enum OpenWindows: String, CaseIterable { case DetailView = "DetailView" // 扩展方法,传递参数到URL func open(with data: SharedData){ let encodedText = data.stringValue.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "" if let url = URL(string: "myapp://\(self.rawValue)?number=\(data.numberValue)&text=\(encodedText)") { NSWorkspace.shared.open(url) } } }
4. 修改DetailView,同步全局状态
同样使用环境对象绑定数据,可选解析URL参数初始化值:
import SwiftUI struct DetailView: View { @EnvironmentObject private var sharedData: SharedData var body: some View { VStack(spacing:30) { Text("Test OK") .navigationTitle("The program") .position(x: 200, y:20) // 绑定全局模型的Int值 TextField("Enter number", value: $sharedData.numberValue, formatter: NumberFormatter()) // 绑定全局模型的String值 TextField("Enter text", text: $sharedData.stringValue) } .frame(width: 400.0, height: 200.0) // 可选:解析URL参数,初始化窗口值 .onAppear { guard let url = NSApplication.shared.currentEvent?.url else { return } let components = URLComponents(url: url, resolvingAgainstBaseURL: false) // 解析Int参数 if let numberStr = components?.queryItems?.first(where: { $0.name == "number" })?.value, let number = Int(numberStr) { sharedData.numberValue = number } // 解析String参数 if let text = components?.queryItems?.first(where: { $0.name == "text" })?.value { sharedData.stringValue = text } } } } struct DetailView_Previews: PreviewProvider { static var previews: some View { DetailView() .environmentObject(SharedData()) } }
核心要点
- @Binding的局限:只能在父子视图间传递状态,跨窗口的独立视图上下文无法使用。
- 全局状态的优势:
@EnvironmentObject/@Observable实现的全局模型,能让所有窗口共享同一份数据,修改时自动同步所有关联视图。 - URL参数的作用:如果需要在打开窗口时传递初始值(而非依赖全局状态的当前值),可以通过URL Query参数传递,并在目标窗口中解析。
内容的提问来源于stack exchange,提问作者Narius
相关产品推荐
相关产品推荐

