visionOS 空間模式:走出視窗之外
在 visionOS 上推出的 App,多數是走 Apple「Designed for iPad」相容路徑進入這個平台的:既有的 iPad 執行檔以一片平面面板的樣子懸浮在 3D 空間中,開發者只要勾選一個選項,而不必真的打造一套 visionOS 原生體驗。對使用者來說,這條路徑沒什麼問題(App 能用),但它低估了這個平台。visionOS 的原生介面提供了三種呈現方式(Windows、Volumes、Immersive Spaces),外加 iPad SDK 所沒有的結構性 UI 元素(Ornaments、Attachments)。4 採用這些元素的 App 感覺就是原生的;沒有採用的,看起來就是「跑在 Vision 上的 iPad App」。
本文以 Apple 官方文件為依據,逐一走過這套空間語彙。切入的角度是「這個平台實際上給了一個 SwiftUI App 什麼」,而不是 visionOS 入門介紹。本系列的 RealityKit 與空間心智模型 一文談的是 3D 內容層;本文談的則是承載它的那層 SwiftUI 介面。
重點摘要
- visionOS App 由三種 scene 型別組合而成:
WindowGroup(Windows)、加上.windowStyle(.volumetric)的WindowGroup(Volumes),以及ImmersiveSpace(Immersive Spaces)1。 - Window 是一個 2D 平面;Volume 是一塊有邊界的 3D 區域;Immersive Space 則環繞在使用者四周。三者規則各不相同:Volume 建立後尺寸固定不可變,Immersive Space 必須明確開啟與關閉,Window 的行為則最接近 iPad。
- 沉浸感分為三種樣式:
.mixed(虛擬內容與實際空間共存)、.full(實際空間被虛擬環境取代)、.progressive(介於兩者之間,保留周邊的空間感)2。 - Ornaments 是與 Window 平行、並在 z 軸上略往前擺放的 UI 平面。visionOS 的工具列與標籤列就是這樣做出來的3。Attachments 則把 SwiftUI 視圖嵌入 RealityView 的 3D 內容之中,是平面 UI 與空間幾何之間的橋樑。
- 「面板 App」反模式:直接把 iPad UI 當成一個 Window 出貨,完全不採用 Volume、Space 或 Ornament。使用者確實能用這個 App,但平台真正的價值完全沒被兌現。
三種 Scene 型別
一個 visionOS App 的 App body,由三類 scene 組合而成,每一類對應到不同的使用者心智模型。
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 scene 同樣是 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 讓需要的 App 提供可縮放的 volume;不過固定尺寸的預設值仍是最常見的情況。這代表:預設尺寸要慎選,因為除非開發者明確選擇啟用,多數 volume 是無法縮放的。
適合用 Volume 的,是那些「空間邊界本身就是體驗一部分」的 App:一座讓使用者繞著走的虛擬雕塑、一把固定在真實牆面上的捲尺、一個以深度錯落配置標靶的健身場景。只是想要更大畫布的 App,用 Volume 得不到什麼好處;把 Window 開大一點才是對的答案。
Immersive Spaces:環繞式體驗
ImmersiveSpace 是一種佔據使用者周遭環境的 scene。5 不同於 Window 或 Volume(兩者都在 Shared Space 中與其他 App 並存),Immersive Space 會接管使用者的周遭環境,也讓其他 App 的視窗無法同時使用。
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
ImmersiveSpace(id: "training") {
TrainingScene()
}
.immersionStyle(selection: .constant(.mixed), in: .mixed, .progressive, .full)
}
}
.immersionStyle(...) 修飾器決定體驗的層級:
.mixed。 虛擬內容與真實空間並存。適合那些讓使用者同時受益於兩種脈絡的 App。.progressive。 部分沉浸,使用者可用 Digital Crown 調高或調低。中央視野是虛擬的,周邊仍保有對實際空間的感知。.full。 實際空間被虛擬環境取代。用於完全沉浸的體驗(冥想、訓練模擬、遊戲)。
開啟 Immersive Space 必須是明確的動作。App 以 space 的 id 呼叫 @Environment(\.openImmersiveSpace);系統負責過場動畫,並關閉任何互相衝突的 space:
@Environment(\.openImmersiveSpace) var openImmersiveSpace
@Environment(\.dismissImmersiveSpace) var dismissImmersiveSpace
Button("Start Session") {
Task {
await openImmersiveSpace(id: "training")
}
}
同一個 App 在同一時間只能有一個 Immersive Space 處於啟用狀態。要在不同 Space 之間切換(例如從 .mixed 換到 .full),必須明確關閉舊的 Space,再開啟新的。
Ornaments:環繞在 Window 周圍的 UI 平面
Ornaments 是附著在 Window 邊緣的 SwiftUI 視圖,在 z 軸上略微擺在 Window 平面之前。visionOS 的工具列、標籤列和輔助控制項就是這麼做出來的。系統本身處處在用: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:把 SwiftUI 放進 3D 空間
當 App 需要在 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 這一層處理。
「面板 App」反模式
visionOS 上最常見的出貨錯誤就是面板 App:一個透過「Designed for iPad」相容性登陸 visionOS 的 iPad App,就以單一 Window 出貨,沒有 Volume、沒有 Immersive Space、也沒有 Ornaments。App 能動,但它配不上這個平台。
判斷一個 App 是不是面板 App,有三個訊號:
只有單一 Window scene。 沒有 .windowStyle(.volumetric),也沒有宣告 ImmersiveSpace。整個 App 就是一片平面,如此而已。
完全沒採用 ornaments。 App 的標籤列長在 Window 內容裡,而不是放到外面。結果就是在相同的內容密度下,比 visionOS 原生 App 顯得更擁擠。
沒有任何只有空間才做得到的功能。 這個 App 完全沒有用到第三軸:Volume 裡沒有 3D 模型,Space 裡沒有環境場景,也沒有透過 attachments 做出 z 軸定位的 UI。它做的事跟在 iPad 上一模一樣,只是浮在空中而已。
面板 App 並不算失敗;對那些本來就不受益於空間運算的內容類型(聊天 App、筆記 App、設定工具)來說,這就是正確的選擇。真正的失敗模式,是出了一個面板 App,卻宣稱它具備 visionOS 原生的權威。本系列的 Apple 平台矩陣 一文主張:要不要進入某個平台是一個產品決策;對 visionOS 而言,這個決策是「這個 App 該不該去掙得空間介面,還是一片面板就夠了?」
常見的失敗
三種會做出糟糕 visionOS UX 的模式:
名為 Volume,其實只是加了深度留白的 2D 內容。 一套填滿 Volume、內部卻只渲染平面的「3D」UI,等於浪費了空間。Volume 是給 3D 內容用的;平面內容該待在 Window 裡。
沉浸樣式與使用情境相牴觸。 一個只提供 .full 沉浸的冥想 App,會為了短短一次練習就把使用者硬拉出所處環境。一個只提供 .mixed 的訓練 App,對需要完全專注的鍛鍊來說又走得不夠遠。沉浸樣式要對得上使用者真實的使用情境。
Ornaments 與內容爭搶注意力。 Ornaments 在設計上就是周邊性的。一個要求中央注意力的 ornament(閃爍的顏色、動態的位移)就失去了它存在的意義。ornaments 該用來承載穩定、瞄一眼就懂的控制項。
這套模式對 visionOS App 的意義
三個要點。
-
依使用者的心智模型挑選 scene 型別,而不是挑好做的那個。 一份平面的項目清單是 Window。一個讓使用者端詳的 3D 模型是 Volume。一個環繞四周的環境是 Immersive Space。在同一個 App 裡混用它們(一個 Window,需要時開出 Volume;從 Window 的按鈕進入 Immersive Space),才是 visionOS 原生的做法。
-
工具列與輔助 UI 一律採用 ornaments。 visionOS 就是靠 ornaments 傳達「這組 UI 是輔助性的」;把工具列塞進 Window 內容裡,看起來就是跑在 Vision 上的 iPad App。整合的成本很小,視覺上的差距卻很大。
-
RealityView 裡的世界內 UI,請用 attachments。 3D 物件上的標籤、虛擬內容旁的按鈕、隨情境出現的讀數。SwiftUI 與 3D 空間之間的橋樑已經有現成解法;失敗模式是不用它,最後淪落到自己土法煉鋼渲染 3D 文字。
完整的 Apple 生態系列:具型別的 App Intents;MCP 伺服器;路由的抉擇;Foundation Models;執行環境與工具鏈 LLM 的分野;三種介面;單一事實來源模式;兩座 MCP 伺服器;Apple 開發用的掛鉤;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 代理開發指南。
常見問題
Volume 和 Immersive Space 有什麼差別?
Volume 是一塊有邊界的 3D 區域,存在於 Shared Space 中,與其他 App 並存。使用者可以繞著它走,系統會為它加上外框,其他 App 的 Window 也仍然看得見。Immersive Space 則環繞使用者、接管整個環境,並讓其他 App 無法同時使用。Volume 是為了「看看這個 3D 東西」;Space 是為了「置身於這個環境之中」。
可以同時開啟多個 Volume 嗎?
可以。多個採用 .volumetric 的 WindowGroup scene 能同時開啟,各自有自己的尺寸與內容。系統會在空間中獨立擺放它們。
可以同時開啟多個 Immersive Space 嗎?
不行。每個 App 同一時間只能有一個 Immersive Space 處於啟用狀態。要在不同 Space 之間切換,必須透過 @Environment(\.openImmersiveSpace) 與 @Environment(\.dismissImmersiveSpace) 明確關閉目前這個,再開啟新的。
Volume 尺寸真的完全不可變嗎?
Volume 的邊界預設在開啟當下就固定下來;visionOS HIG 的立場是,Volume 代表的是帶有明確意圖邊界的特定 3D 內容,任意讓使用者縮放會扭曲內容原本設定的比例。visionOS 2 之後為可縮放的 volume 加入了開發者可選擇啟用的機制,透過 .windowResizability(.contentSize) 及相關 API 提出需求,因此需要讓使用者調整空間容器大小的 App 是有辦法做到的。多數 volume 仍以固定的預設值出貨,而對具有特定比例的內容(虛擬雕塑、按實際尺寸建模的物件),HIG 也持續建議如此。
要怎麼在 visionOS 的 Window 裡加上標籤列?
若要做內容內的分頁(iPad 式的做法),在 Window 裡用 TabView;若要做 visionOS 原生的周邊式標籤 UI,則用 ornament 搭配自訂的按鈕列。ornament 這條路正是 Apple 自家 App(Music、Mail)採用的做法,對 visionOS 使用者來說也最有原生感。
RealityView 的 attachments 可以搭配手部追蹤互動嗎?
可以。attachments 一旦定位完成就是 3D 實體,與其他 RealityKit 實體共用同一套手勢與 hit-testing 系統。點按、拖曳與 hover 手勢透過 SwiftUI 標準的手勢修飾器附加到它們身上;本系列的 RealityKit 一文 涵蓋了手部追蹤的整合模式。
參考資料
-
Apple Developer: Meet SwiftUI for spatial computing(WWDC 2023 場次 10109)。介紹 WindowGroup、volumetric WindowGroup 與 ImmersiveSpace 這三種 visionOS scene 型別。 ↩
-
Apple Developer Documentation:
ImmersionStyle。三種沉浸樣式(.mixed、.progressive、.full)與.immersionStyle(selection:in:)修飾器 API。 ↩ -
Apple Developer Documentation:
ornament(visibility:attachmentAnchor:contentAlignment:ornament:)。這個 SwiftUI 視圖修飾器會依指定的錨點,為 Window 加上一片 ornament UI 平面。 ↩ -
Apple Developer: Go beyond the window with SwiftUI(WWDC 2023 場次 10111)。這場議程涵蓋 Volumes、Immersive Spaces,以及在 visionOS 上跳脫平面面板 UI 的各種模式。 ↩
-
Apple Developer Documentation: Creating an immersive space in visionOS with SwiftUI。定義與開啟沉浸式空間的完整指南。 ↩