← 所有文章

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 的意義

三個要點。

  1. 依使用者的心智模型挑選 scene 型別,而不是挑好做的那個。 一份平面的項目清單是 Window。一個讓使用者端詳的 3D 模型是 Volume。一個環繞四周的環境是 Immersive Space。在同一個 App 裡混用它們(一個 Window,需要時開出 Volume;從 Window 的按鈕進入 Immersive Space),才是 visionOS 原生的做法。

  2. 工具列與輔助 UI 一律採用 ornaments。 visionOS 就是靠 ornaments 傳達「這組 UI 是輔助性的」;把工具列塞進 Window 內容裡,看起來就是跑在 Vision 上的 iPad App。整合的成本很小,視覺上的差距卻很大。

  3. RealityView 裡的世界內 UI,請用 attachments。 3D 物件上的標籤、虛擬內容旁的按鈕、隨情境出現的讀數。SwiftUI 與 3D 空間之間的橋樑已經有現成解法;失敗模式是不用它,最後淪落到自己土法煉鋼渲染 3D 文字。

完整的 Apple 生態系列:具型別的 App IntentsMCP 伺服器路由的抉擇Foundation Models執行環境與工具鏈 LLM 的分野三種介面單一事實來源模式兩座 MCP 伺服器Apple 開發用的掛鉤Live ActivitieswatchOS 執行環境SwiftUI 的內部構造RealityKit 的空間心智模型SwiftData 綱要紀律Liquid Glass 模式跨平台出貨平台矩陣Vision 框架Symbol EffectsCore ML 裝置端推論採用 Writing Tools APISwift TestingPrivacy Manifest 深入解析把無障礙當成平台能力SF Pro 字體系統我拒絕書寫的主題。系列首頁在 Apple 生態系列。若想了解更廣義的「iOS 搭配 AI 代理」脈絡,請參閱 iOS 代理開發指南

常見問題

Volume 和 Immersive Space 有什麼差別?

Volume 是一塊有邊界的 3D 區域,存在於 Shared Space 中,與其他 App 並存。使用者可以繞著它走,系統會為它加上外框,其他 App 的 Window 也仍然看得見。Immersive Space 則環繞使用者、接管整個環境,並讓其他 App 無法同時使用。Volume 是為了「看看這個 3D 東西」;Space 是為了「置身於這個環境之中」。

可以同時開啟多個 Volume 嗎?

可以。多個採用 .volumetricWindowGroup 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 一文 涵蓋了手部追蹤的整合模式。

參考資料


  1. Apple Developer: Meet SwiftUI for spatial computing(WWDC 2023 場次 10109)。介紹 WindowGroup、volumetric WindowGroup 與 ImmersiveSpace 這三種 visionOS scene 型別。 

  2. Apple Developer Documentation: ImmersionStyle。三種沉浸樣式(.mixed.progressive.full)與 .immersionStyle(selection:in:) 修飾器 API。 

  3. Apple Developer Documentation: ornament(visibility:attachmentAnchor:contentAlignment:ornament:)。這個 SwiftUI 視圖修飾器會依指定的錨點,為 Window 加上一片 ornament UI 平面。 

  4. Apple Developer: Go beyond the window with SwiftUI(WWDC 2023 場次 10111)。這場議程涵蓋 Volumes、Immersive Spaces,以及在 visionOS 上跳脫平面面板 UI 的各種模式。 

  5. Apple Developer Documentation: Creating an immersive space in visionOS with SwiftUI。定義與開啟沉浸式空間的完整指南。 

相關文章

RealityKit與空間思維模型

RealityKit是一套實體—元件—系統架構,而非3D版的SwiftUI。錨點將實體放置於真實空間中。此模型與視窗有五種根本差異。

12 分鐘閱讀

2026 年的 RealityKit 與 Reality Composer Pro 3

WWDC26 讓 Apple 的空間流程趨於成熟:RealityKit 新增光照與布料模擬,並推出具備即時預覽與 Xcode 外掛的獨立版 Reality Composer Pro 3。

13 分鐘閱讀

安裝與更新 Codex CLI:Mac、Linux、Windows

安裝、更新、鎖定版本與移除 OpenAI Codex CLI 的每一種方式:安裝指令碼、npm、Homebrew、winget,涵蓋 macOS、Linux、WSL 與 Windows。

9 分鐘閱讀