visionOS 超越窗口的空间模式
大多数在 visionOS 上发布的应用都是通过 Apple 的”Designed for iPad”兼容路径进入该平台的:现有的 iPad 二进制文件作为悬浮在 3D 空间中的扁平面板运行,开发者只需勾选一个选项,而无需构建 visionOS 原生体验。这条路径对用户来说没问题(应用能用),但它低估了平台的价值。visionOS 的原生界面为开发者提供了三种呈现方式(Windows、Volumes 和 Immersive Spaces),以及 iPad SDK 所没有的结构化 UI 原语(Ornaments、Attachments)。采用这些方式的应用感觉是原生的;不采用的应用读起来就像是 Vision 上的 iPad。
本文将对照 Apple 的文档梳理空间词汇。视角是”该平台实际为 SwiftUI 应用提供了什么”,而不是 visionOS 入门。本系列的 RealityKit 与空间心智模型 一文涵盖了 3D 内容层;本文涵盖的是包含它的 SwiftUI 表面。
TL;DR
- visionOS 应用由三种场景类型组成:
WindowGroup(Windows)、带.windowStyle(.volumetric)的WindowGroup(Volumes),以及ImmersiveSpace(Immersive Spaces)1。 - Window 是 2D 平面;Volume 是有边界的 3D 区域;Immersive Space 环绕用户。各自有不同的规则:Volumes 创建后尺寸不可变,Immersive Spaces 需要显式打开/关闭,Windows 的行为最接近 iPad。
- 沉浸感分为三种风格:
.mixed(内容与房间共存)、.full(房间被虚拟环境替换)、.progressive(中间地带,保留外围接地感)2。 - Ornaments 是平行于 Window 且在 z 轴上更靠前的 UI 平面。它们是 visionOS 实现工具栏和标签栏的方式3。Attachments 将 SwiftUI 视图嵌入 RealityView 中的 3D 内容里,是扁平 UI 与空间几何之间的桥梁。
- “面板应用”反模式:将 iPad UI 作为 Window 发布,不采用 Volume、Space 或 Ornament。用户可以使用该应用,但平台的真正价值未被利用。
三种场景类型
visionOS 应用的 App 主体由三种类的场景组合而成。每一种都有独特的用户心智模型。
Windows:2D 平面
WindowGroup 默认生成带有 visionOS 玻璃边框的 2D Window。Window 被定位在空间中(系统将其放置在用户视线前方),用户通过标准系统手势移动或调整其大小。从 SwiftUI 的角度看,Window 是 visionOS 中 macOS 窗口的对应物:一个具有深度感知玻璃材质的扁平内容表面。
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
默认的 Window 在其内容周围有玻璃材质。希望表面完全透明的应用使用 .windowStyle(.plain):
WindowGroup {
ContentView()
}
.windowStyle(.plain)
Plain 风格的 Window 会失去系统玻璃边框。当内容自带视觉容器时使用它;否则默认值是正确的。
Volumes:有边界的 3D 区域
Volume 是包含深度感知内容的 3D 区域(一个模型、一个包含多个对象的场景、一个受益于第三轴的 UI)。volume 场景同样是 WindowGroup,只是风格不同:
WindowGroup(id: "globe") {
GlobeView()
}
.windowStyle(.volumetric)
.defaultSize(width: 0.6, height: 0.6, depth: 0.6, in: .meters)
.defaultSize(width:height:depth:in:) 修饰符以现实世界单位(米)指定 volume 的边界。默认情况下,边界在打开时被固定,用户可以移动 volume,但无法调整其大小。visionOS 2+ 通过 .windowResizability(.contentSize) 及相关 API 为希望支持用户可调整大小 volumes 的应用增加了一条选择性接入路径;固定大小的默认值仍是最常见的情况。这意味着:要谨慎选择默认大小,因为除非开发者明确启用,否则大多数 volumes 是不可调整大小的。
适合使用 Volumes 的场景是空间边界本身就是体验的一部分:用户可以绕着走的虚拟雕塑、固定在真实墙面上的卷尺、带有深度错落目标的健身场景。只是想要更宽画布的应用并不需要 Volume;更大的 Window 才是正确答案。
Immersive Spaces:环绕
ImmersiveSpace 是占据用户周围环境的场景。与 Window 或 Volume 不同(两者都在 Shared Space 中与其他应用一起可见),Immersive Space 接管用户的周围环境,并阻止同时使用其他应用的窗口。
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
ImmersiveSpace(id: "training") {
TrainingScene()
}
.immersionStyle(selection: .constant(.mixed), in: .mixed, .progressive, .full)
}
}
.immersionStyle(...) 修饰符选择体验层级:
.mixed。 虚拟内容与真实房间一起呈现。适用于用户能够同时受益于两种语境的应用。.progressive。 部分沉浸,使用 Digital Crown 调高或调低。用户在中央视野为虚拟内容时仍保留对房间的外围感知。.full。 房间被虚拟环境取代。用于完全沉浸的体验(冥想、训练模拟、游戏)。
打开 Immersive Space 是显式的。应用调用 @Environment(\.openImmersiveSpace) 并传入 space 的 id;系统处理过渡动画以及关闭任何冲突 space 的操作:
@Environment(\.openImmersiveSpace) var openImmersiveSpace
@Environment(\.dismissImmersiveSpace) var dismissImmersiveSpace
Button("Start Session") {
Task {
await openImmersiveSpace(id: "training")
}
}
每个应用同一时间只能有一个 Immersive Space 处于活动状态。Spaces 之间的切换(例如从 .mixed 到 .full)需要显式关闭旧的 Space 并打开新的。
Ornaments:Window 周围的 UI 平面
Ornaments 是附加在 Window 边缘的 SwiftUI 视图,在 z 轴上略微位于 Window 平面之前。它们是 visionOS 实现工具栏、标签栏和辅助控件的方式。系统在各处都使用 ornaments:TV 中的播放控件、Music 中的分段控件、Mail 中的工具栏。
ContentView()
.ornament(
attachmentAnchor: .scene(.bottom),
contentAlignment: .center
) {
HStack {
Button("Previous", systemImage: "backward.fill") { ... }
Button("Play", systemImage: "play.fill") { ... }
Button("Next", systemImage: "forward.fill") { ... }
}
.padding()
.glassBackgroundEffect()
}
attachmentAnchor: 参数指定 ornament 相对于 Window 的位置:.scene(.top)、.scene(.bottom)、.scene(.leading)、.scene(.trailing)。Ornament 的视觉处理由开发者负责;.glassBackgroundEffect() 生成与 Window 边框匹配的 visionOS 原生玻璃材质。
Ornaments 解决了 visionOS 上的一个真实问题:把控件放在 Window 内部会挤占内容;放在单独的 Window 中又迫使用户重新对准目光。Ornament 悬浮在用户的外围视野中,可被目光定位,但不会与主要内容争抢中央视图。
RealityView Attachments:3D 空间中的 SwiftUI
当应用需要在 3D 场景中嵌入 SwiftUI 视图时(3D 模型上的标签、悬浮在虚拟物体附近的按钮、固定在真实表面上的测量读数),桥梁就是 RealityView 的 attachments 机制。
RealityView { content, attachments in
let model = ModelEntity(...)
content.add(model)
if let label = attachments.entity(for: "label") {
label.position = [0, 0.5, 0]
model.addChild(label)
}
} attachments: {
Attachment(id: "label") {
Text("Vintage Globe, 1872")
.padding()
.glassBackgroundEffect()
}
}
attachments: 闭包以稳定标识符声明 SwiftUI 视图。在主 RealityView 闭包内,attachments.entity(for:) 检索该视图作为可在场景坐标空间中定位的 3D Entity。该视图参与 SwiftUI 的更新周期(状态变化会重新渲染视图),同时作为带纹理的平面渲染在 3D 场景中。
这一机制对任何位于世界中的 UI 都是合适的:跟随移动物体的标签、测量注释、上下文按钮。SwiftUI 视图的编写方式不变;3D 定位发生在 RealityView 层。
“面板应用”反模式
最常见的 visionOS 发布错误就是面板应用:通过”Designed for iPad”兼容方式进入 visionOS 的 iPad 应用,作为单一 Window 发布,没有 Volume、没有 Immersive Space、没有 Ornaments。应用能用,但它对不起这个平台。
判断一个应用是面板应用的三个信号:
单一 Window 场景。 没有 .windowStyle(.volumetric),没有声明的 ImmersiveSpace。应用就是一个扁平表面,仅此而已。
未采用 ornaments。 应用的标签栏位于 Window 内容内部,而不是外部。在相同的内容密度下,结果比 visionOS 原生应用更拥挤。
没有空间专属功能。 应用没有为任何东西使用第三轴:Volume 中没有 3D 模型、Space 中没有环境场景、没有通过 attachments 实现的 z 轴定位 UI。应用做的事情和在 iPad 上一模一样,只是漂浮起来。
面板应用并非失败;对于不受益于空间计算的内容类别(聊天应用、笔记应用、设置工具),它们是正确的选择。失败模式是发布面板应用并为其声称 visionOS 原生权威性。本系列的 Apple 平台矩阵 一文论证了平台纳入是一个产品决策;对于 visionOS,这个决策是”这个应用应该赢得空间表面,还是面板就够用?”
常见失败
三种产生糟糕 visionOS UX 的模式:
实际上是带深度填充的 2D 内容的 Volumes。 一个填满 Volume 但内部渲染扁平面板的”3D”UI 会浪费空间。Volumes 是为 3D 内容设计的;扁平内容属于 Window。
与使用场景相冲突的沉浸风格。 仅发布 .full 沉浸的冥想应用,会迫使用户在短时段内脱离自己的环境。仅发布 .mixed 的训练应用,对完全专注的练习来说不够深入。让沉浸风格匹配用户的实际使用场景。
与内容争抢的 Ornaments。 Ornaments 在设计上就是外围的。要求中央注意力的 ornament(闪烁的颜色、动画运动)违背了初衷。Ornaments 应用于稳定的、可一瞥即知的控件。
这一模式对 visionOS 应用意味着什么
三个要点。
-
按用户心智模型而非便利性来选择场景类型。 一份扁平的项目列表是 Window。用户审视的 3D 模型是 Volume。环绕的环境是 Immersive Space。在一个应用中混用它们(带有按需打开 Volume 的 Window、可从 Window 按钮访问的 Immersive Space)才是 visionOS 原生的模式。
-
为工具栏和辅助 UI 采用 ornaments。 Ornaments 是 visionOS 传达”此 UI 是辅助性的”的方式;把工具栏放在 Window 内容内部读起来就像 Vision 上的 iPad。集成的工作量很小,而视觉差异巨大。
-
在 RealityView 中使用 attachments 实现位于世界中的 UI。 3D 物体上的标签、虚拟内容附近的按钮、上下文读数。SwiftUI 与 3D 空间之间的桥梁已经解决;失败模式是不使用它,反而最终采用临时拼凑的 3D 文本渲染。
完整的 Apple 生态系列:类型化的 App Intents;MCP 服务器;路由问题;Foundation Models;运行时与工具 LLM 的区分;三种界面;单一事实来源模式;两个 MCP 服务器;Apple 开发的 hooks;Live Activities;watchOS 运行时;SwiftUI 内部;RealityKit 的空间心智模型;SwiftData 模式纪律;Liquid Glass 模式;多平台发布;平台矩阵;Vision 框架;Symbol Effects;Core ML 推理;Writing Tools API;Swift Testing;Privacy Manifest;作为平台的可访问性;SF Pro 字体排印;我拒绝写的东西。中心位于 Apple 生态系列。更广泛的 iOS 与 AI 智能体语境,请参见 iOS 智能体开发指南。
FAQ
Volume 与 Immersive Space 有什么区别?
Volume 是有边界的 3D 区域,与其他应用一起存在于 Shared Space 中。用户可以绕着它走动,系统为其加上框架,其他应用的 Windows 仍然可见。Immersive Space 环绕用户、接管环境,并阻止同时使用其他应用。Volumes 是为了”看这个 3D 物体”;Spaces 是为了”身处这个环境”。
我可以同时打开多个 Volumes 吗?
可以。多个带 .volumetric 的 WindowGroup 场景可以同时打开,每个都有自己的尺寸和内容。系统在空间中独立地定位它们。
我可以同时打开多个 Immersive Spaces 吗?
不可以。每个应用同一时间只能有一个 Immersive Space 处于活动状态。在 Spaces 之间切换需要通过 @Environment(\.openImmersiveSpace) 和 @Environment(\.dismissImmersiveSpace) 显式关闭当前的 Space 并打开新的。
Volume 的尺寸真的不可变吗?
Volume 边界默认在打开时是固定的;visionOS HIG 的设定是 Volumes 代表具有特定边界意图的 3D 内容,任意的用户调整大小会扭曲内容的预期比例。visionOS 2+ 通过 .windowResizability(.contentSize) 及相关 API 为可调整大小的 volumes 增加了开发者选择性接入,因此需要用户可调整大小空间容器的应用可以请求该能力。大多数 volumes 仍以固定默认值发布,HIG 也持续推荐这种方式用于具有特定比例的内容(虚拟雕塑、按物理尺寸建模的模型)。
如何向 visionOS Window 添加标签栏?
使用 Window 内部的 TabView 实现内容内标签(iPad 风格模式),或使用带有自定义按钮行的 ornament 实现 visionOS 原生的外围标签 UI。ornament 路径是 Apple 自家应用(Music、Mail)所采用的,对 visionOS 用户来说也最有原生感。
RealityView attachments 可以与手部追踪交互吗?
可以。Attachments 在被定位后就是 3D 实体,它们与其他 RealityKit 实体一样参与同一个手势和命中测试系统。点按、拖动和悬停手势通过 SwiftUI 的标准手势修饰符附加到它们上;本系列的 RealityKit 一文 涵盖了手部追踪集成模式。
参考
-
Apple Developer:Meet SwiftUI for spatial computing(WWDC 2023 session 10109)。介绍了 WindowGroup、volumetric WindowGroup 和 ImmersiveSpace 这三种 visionOS 场景类型。 ↩
-
Apple Developer 文档:
ImmersionStyle。三种沉浸风格(.mixed、.progressive、.full)以及.immersionStyle(selection:in:)修饰符 API。 ↩ -
Apple Developer 文档:
ornament(visibility:attachmentAnchor:contentAlignment:ornament:)。该 SwiftUI 视图修饰符将一个 ornament UI 平面以指定锚点添加到 Window。 ↩ -
Apple Developer:Go beyond the window with SwiftUI(WWDC 2023 session 10111)。该 session 涵盖 Volumes、Immersive Spaces,以及在 visionOS 上超越扁平面板 UI 的模式。 ↩
-
Apple Developer 文档:Creating an immersive space in visionOS with SwiftUI。定义和打开 immersive spaces 的端到端指南。 ↩