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

如何在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 00:48:24