SwiftUI 窗口、Scene 与 多窗口架构

发布: (2025年12月29日 GMT+8 06:42)
4 min read
原文: Dev.to

Source: Dev.to

App vs Scene(最重要的区别)

App
定义你的应用是什么,拥有全局配置,并创建 scene。

Scene
定义你的应用如何呈现,拥有窗口生命周期,并且可以同时存在多个实例。

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            RootView()
        }
    }
}

规则: 你的应用可以拥有多个 scene,每个 scene 可以拥有多个窗口。

WindowGroup 实际做了什么

WindowGroup {
    ContentView()
}
  • iOS: 多个应用窗口(iPad)
  • macOS: 多个窗口
  • visionOS: 多个空间实例

每个窗口:

  • 拥有自己的视图层级
  • 拥有自己的状态
  • 不会 自动共享 ViewModel

最大的多窗口 Bug

@StateObject var vm = GlobalViewModel()

WindowGroup 内部放置 @StateObject 是错误的,因为:

  • 每个窗口都会得到一个新实例
  • 状态在窗口之间分叉
  • 导航会不同步

正确的全局状态放置位置

全局状态必须位于 scene 之上

@main
struct MyApp: App {
    @StateObject private var appState = AppState()

    var body: some Scene {
        WindowGroup {
            RootView()
                .environmentObject(appState)
        }
    }
}

现在:

  • 所有窗口共享同一状态
  • 导航保持一致
  • 数据保持同步

Scene‑Local 状态 vs. App‑Global 状态

Scene‑local(场景本地):

  • 导航栈
  • 选中项
  • 焦点
  • 滚动位置

App‑global(应用全局):

  • 认证
  • 用户会话
  • 缓存
  • 功能标记
  • 深度链接

切勿混用它们。

ScenePhase 是每个 Scene 的(而非全局的)

@Environment(\.scenePhase) var scenePhase

每个窗口都有自己的阶段,这意味着:

  • 将一个窗口置于后台 ≠ 整个应用进入后台
  • inactive ≠ 被销毁
  • active ≠ 所有窗口都在前台

请明智地使用这些信息。

支持多种 Scene 类型

SwiftUI 允许你声明多种 scene 角色:

var body: some Scene {
    WindowGroup("Main") {
        MainView()
    }

    WindowGroup("Inspector") {
        InspectorView()
    }

    Settings {
        SettingsView()
    }
}

典型用例:

  • 检查器面板
  • 设置窗口
  • 辅助工具
  • 调试覆盖层

编程方式打开新窗口

@Environment(\.openWindow) var openWindow

Button("Open Details") {
    openWindow(id: "details")
}

定义要打开的窗口:

WindowGroup(id: "details") {
    DetailView()
}

这种模式是桌面级 SwiftUI 应用管理额外窗口的方式。

窗口范围的依赖注入

每个窗口应接收:

  • 自己的导航状态
  • 共享的服务
  • 共享的应用状态

示例:

WindowGroup {
    RootView(
        router: Router(),
        services: services
    )
}
  • services 在所有窗口之间共享
  • router(或类似的导航对象)是每个窗口独有的

测试多窗口行为

在测试时,验证:

  • 可以正确打开和关闭多个窗口
  • 将一个 scene 置于后台不会影响其他 scene
  • 状态恢复在每个窗口上都能工作
  • 共享状态的变更能够如预期传播

大多数 SwiftUI bug 只会在两个或更多窗口同时激活时显现。

最后思考

SwiftUI scene 是一种架构概念,而不是样板代码。理解以下要点:

  • App 与 scene 的区别
  • 窗口身份
  • Scene‑local 与 global 状态的划分
  • 多窗口行为

能够帮助你构建:

  • 原生感
  • 正确性
  • 可扩展性的应用。
Back to Blog

相关文章

阅读更多 »

SwiftUI 焦点系统 & 键盘内部

!Sebastien Lato https://media2.dev.to/dynamic/image/width=50,height=50,fit=cover,gravity=auto,format=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads...

SwiftUI 可访问性内部

可访问性是一棵平行视图树 SwiftUI 构建了两棵树: - 可视视图树 - 可访问性树 它们相关——但并不相同。 单个…

ScrollView 与 SwiftUI 中的坐标空间

基于滚动的 UI 在现代应用中随处可见——collapsing headers、parallax effects、sticky toolbars、section pinning、scroll‑driven animations、pull‑to‑refresh……