← すべての記事

visionOSの空間パターン — ウィンドウを超えて

visionOS向けに出荷されるアプリのほとんどは、Appleの「Designed for iPad」互換パスを通じてプラットフォームに到達しています。既存のiPadバイナリが3D空間に浮かぶフラットなパネルとして実行され、開発者はvisionOSネイティブな体験を構築する代わりに、チェックボックスにチェックを入れるだけです。このパスはユーザーにとっては問題ありません(アプリは動作します)が、プラットフォームの価値を十分に引き出していません。visionOSのネイティブな表面は、開発者に3つの提示方法(Windows、Volumes、Immersive Spaces)と構造的なUIプリミティブ(Ornaments、Attachments)を提供しており、これらはiPadのSDKには存在しないものです。これらを採用したアプリはネイティブに感じられますが、そうでないアプリはiPad-on-Visionとして読み取られてしまいます。

この記事では、Appleのドキュメントに沿って空間語彙を辿っていきます。フレームは「visionOS入門」ではなく、「プラットフォームがSwiftUIアプリに対して実際に提供しているもの」です。クラスター内のRealityKitと空間メンタルモデルの記事では3Dコンテンツレイヤーを扱っていますが、本記事ではそれを内包するSwiftUI表面を扱います。

TL;DR

  • visionOSアプリは3つのシーンタイプを構成します。WindowGroup(Windows)、.windowStyle(.volumetric)を伴うWindowGroup(Volumes)、ImmersiveSpace(Immersive Spaces)です1
  • Windowは2D平面、Volumeは3Dの境界領域、Immersive Spaceはユーザーを取り囲みます。それぞれ異なるルールを持ちます。Volumeは生成後にサイズが変更不可、Immersive Spacesは明示的なopen/dismissが必要、WindowsはiPadに最も近い動作をします。
  • Immersionには3つのスタイルがあります。.mixed(コンテンツが部屋と共存)、.full(部屋が仮想環境に置き換わる)、.progressive(周辺の現実感を残した中間段階)2
  • OrnamentsはWindowに平行で、z軸方向に少し前方に配置されるUI平面です。visionOSにおけるツールバーやタブバーの実装方法となります3。AttachmentsはRealityView内の3DコンテンツにSwiftUIビューを埋め込む仕組みで、フラットなUIと空間ジオメトリの橋渡しとなります。
  • 「パネルアプリ」アンチパターン — iPadのUIをWindowとしてそのまま出荷し、Volume、Space、Ornamentのいずれも採用しないこと。ユーザーはアプリを使えますが、プラットフォーム本来の価値が引き出されていません。

3つのシーンタイプ

visionOSアプリのAppボディは、3つのクラスからシーンを構成します。それぞれが異なるユーザーのメンタルモデルを持っています。

Windows — 2D平面

WindowGroupは、デフォルトでvisionOSのガラスフレームを持つ2D Windowを生成します。Windowは空間内に配置され(システムがユーザーの視線の前方に配置します)、標準のシステムジェスチャーによってユーザーが移動・リサイズできます。SwiftUIの観点から見ると、WindowはmacOSウィンドウのvisionOS版です。深度を意識したガラスマテリアルを持つフラットなコンテンツ表面です。

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

デフォルトのWindowは、コンテンツの周囲にガラスマテリアルを持ちます。完全に透明な表面が必要なアプリは、.windowStyle(.plain)を使用します。

WindowGroup {
    ContentView()
}
.windowStyle(.plain)

plainスタイルのWindowはシステムのガラスフレームを失います。コンテンツ自身が視覚的なコンテナを提供する場合に使用してください。それ以外の場合はデフォルトが正解です。

Volumes — 3Dの境界領域

Volumeは、深度を意識したコンテンツ(モデル、複数オブジェクトを含むシーン、第3軸が活きるUI)を内包する3D領域です。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の境界を指定します。デフォルトでは、境界はopen時に固定され、ユーザーはvolumeを移動できますがリサイズはできません。visionOS 2以降では、ユーザーがリサイズ可能なvolumeを実現したいアプリ向けに、.windowResizability(.contentSize)および関連するAPIを通じたオプトインのパスが追加されました。固定サイズのデフォルトが依然として最も一般的なケースです。つまり、デフォルトサイズは慎重に選ぶ必要があります。開発者が明示的にオプトインしない限り、ほとんどのvolumeはリサイズ不可だからです。

Volumeに適した候補は、空間的な境界そのものが体験の一部であるアプリです。ユーザーが周囲を歩き回る仮想彫刻、現実の壁にピン留めされたメジャー、奥行き方向に並んだターゲットを持つワークアウトシーンなどです。単に広いキャンバスが欲しいだけのアプリは、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)をスペースのidとともに呼び出します。システムは遷移アニメーションと、競合するスペースのdismissを処理します。

@Environment(\.openImmersiveSpace) var openImmersiveSpace
@Environment(\.dismissImmersiveSpace) var dismissImmersiveSpace

Button("Start Session") {
    Task {
        await openImmersiveSpace(id: "training")
    }
}

アプリごとに同時にアクティブにできるImmersive Spaceは1つだけです。Spaces間の遷移(例えば.mixedから.fullへの切り替え)には、古いSpaceの明示的なdismissと新しいSpaceのopenが必要です。

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:パラメータは、Windowに対するornamentの位置を指定します。.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の出荷ミスは、パネルアプリです。iPadアプリが「Designed for iPad」互換性を介してvisionOSに届き、Volumeなし、Immersive Spaceなし、Ornamentsなしの単一のWindowとして出荷されるパターンです。アプリは動作しますが、プラットフォームを獲得していません。

アプリがパネルアプリであるかを示す3つのシグナル。

単一のWindowシーン。 .windowStyle(.volumetric)もなく、ImmersiveSpaceの宣言もありません。アプリはフラットな表面、それだけです。

Ornamentsを採用していない。 アプリのタブバーがWindowの外側ではなく、Windowコンテンツの内部に配置されています。結果として、同じコンテンツ密度のvisionOSネイティブアプリよりも混雑して見えます。

空間専用機能がない。 アプリは第3軸を何にも使っていません。Volume内の3Dモデルもなく、Space内の環境シーンもなく、attachmentsを通じたz方向に配置されたUIもありません。アプリはiPad上で行っていたのと同じことを、ただ浮かんでやっているだけです。

パネルアプリは失敗ではありません。空間コンピューティングの恩恵を受けないコンテンツカテゴリ(チャットアプリ、メモアプリ、設定ユーティリティ)にとっては正しい判断です。失敗パターンは、パネルアプリを出荷した上で、それに対してvisionOSネイティブのオーソリティを主張することです。クラスター内のApple Platform Matrix記事では、プラットフォーム包含はプロダクトの判断であると論じています。visionOSの場合、判断は「このアプリは空間表面を獲得すべきか、それともパネルで十分か?」です。

よくある失敗

visionOSのUXを悪化させる3つのパターン。

実際は深さパディング付きの2DコンテンツであるVolumes。 Volumeを満たすが内部にフラットな平面をレンダリングする「3D」UIは、空間を無駄にします。Volumeは3Dコンテンツのためのものです。フラットなコンテンツはWindowに属します。

ユースケースと噛み合わないimmersionスタイル。 .fullの没入のみを提供する瞑想アプリは、短いセッションでもユーザーを環境から引き離してしまいます。.mixedのみを提供する訓練アプリは、完全に集中するエクササイズには物足りません。immersionスタイルはユーザーの実際のセッションに合わせるべきです。

コンテンツと競合するOrnaments。 Ornamentsは設計上、周辺的なものです。中央の注意を要求するornament(点滅する色、アニメーション動作)は本来の目的を損ないます。Ornamentsは安定した、ちらりと見られるコントロールに使用してください。

このパターンがvisionOSアプリにもたらす意味

3つのポイント。

  1. シーンタイプは、簡単さではなくユーザーのメンタルモデルで選ぶ。 フラットな項目リストはWindowです。ユーザーが検査する3DモデルはVolumeです。取り囲む環境はImmersive Spaceです。これらを1つのアプリで組み合わせること(オンデマンドでVolumeを開くWindow、WindowのボタンからアクセスできるImmersive Space)が、visionOSネイティブのパターンです。

  2. ツールバーや補助UIにはOrnamentsを採用する。 Ornamentsは「このUIは補助的である」とvisionOSが伝える方法です。ツールバーをWindowコンテンツ内に配置するとiPad-on-Visionとして読み取られます。統合は小さく、視覚的な違いは大きいです。

  3. RealityView内の世界内UIにはattachmentsを使う。 3Dオブジェクト上のラベル、仮想コンテンツ近くのボタン、コンテキスト読み取り。SwiftUIと3D空間の橋渡しは解決済みです。失敗パターンはこれを使わずに、その場しのぎの3Dテキストレンダリングに陥ることです。

Apple Ecosystemクラスターの完全版 — 型付きApp IntentsMCPサーバールーティングの問題Foundation ModelsランタイムとツーリングLLMの区別3つの表面単一の真実の源パターン2つのMCPサーバーApple開発のためのhooksLive ActivitieswatchOSランタイムSwiftUI内部RealityKitの空間メンタルモデルSwiftDataスキーマ規律Liquid Glassパターンマルチプラットフォーム出荷プラットフォームマトリクスVisionフレームワークSymbol EffectsCore MLオンデバイス推論Writing Tools APISwift TestingPrivacy ManifestプラットフォームとしてのアクセシビリティSF Proタイポグラフィ書くことを拒むテーマ。ハブはApple Ecosystem Seriesにあります。AIエージェントを伴うiOSのより広い文脈については、iOS Agent Developmentガイドをご覧ください。

FAQ

VolumeとImmersive Spaceの違いは何ですか?

Volumeは境界のある3D領域で、他のアプリと並んでShared Spaceに存在します。ユーザーは周囲を歩き回ることができ、システムがフレームを与え、他のアプリのWindowは表示されたままです。Immersive Spaceはユーザーを取り囲み、環境を引き継ぎ、他のアプリの同時使用を防ぎます。Volumeは「この3Dを見る」ためのもの、Spaceは「この環境にいる」ためのものです。

複数のVolumeを同時に開けますか?

はい。.volumetricを伴う複数のWindowGroupシーンを同時に開くことができ、それぞれ独自のサイズとコンテンツを持ちます。システムが空間内で独立して配置します。

複数のImmersive Spaceを同時に開けますか?

いいえ。アプリごとに同時にアクティブにできるImmersive Spaceは1つだけです。Space間の切り替えには、@Environment(\.openImmersiveSpace)@Environment(\.dismissImmersiveSpace)を介して、現在のSpaceの明示的なdismissと新しいSpaceのopenが必要です。

Volumeのサイズは本当に変更不可ですか?

Volumeの境界はデフォルトでopen時に固定されます。visionOS HIGの位置づけでは、Volumeは意図的な境界を持つ特定の3Dコンテンツを表すものであり、ユーザーによる任意のリサイズはコンテンツの意図したスケールを歪めるとされています。visionOS 2以降では、リサイズ可能なvolumeのために.windowResizability(.contentSize)および関連するAPIを通じた開発者向けのオプトインが追加されたため、ユーザーがリサイズ可能な空間コンテナが必要なアプリはそれを要求できます。ほとんどのvolumeは固定デフォルトで出荷されており、HIGは特定のスケールを持つコンテンツ(仮想彫刻、物理的サイズのモデル)に対して引き続きこれを推奨しています。

visionOS Windowにタブバーを追加するにはどうすればいいですか?

コンテンツ内タブにはWindow内でTabViewを使用します(iPadスタイルのパターン)。あるいは、visionOSネイティブの周辺タブUIには、カスタムボタン行を持つornamentを使用します。ornamentパスはApple自身のアプリ(Music、Mail)が採用しているもので、visionOSユーザーにとって最もネイティブに感じられます。

RealityView attachmentsはハンドトラッキングと連携できますか?

はい。配置されたattachmentsは3Dエンティティであり、他のRealityKitエンティティと同じジェスチャー・ヒットテストシステムに参加します。タップ、ドラッグ、ホバーのジェスチャーは、SwiftUIの標準ジェスチャー修飾子を介して取り付けられます。クラスター内のRealityKit記事では、ハンドトラッキング統合パターンを扱っています。

参考文献


  1. Apple Developer: Meet SwiftUI for spatial computing(WWDC 2023 セッション10109)。WindowGroup、volumetric WindowGroup、ImmersiveSpaceの3つのvisionOSシーンタイプの紹介。 

  2. Apple Developer Documentation: ImmersionStyle。3つのimmersionスタイル(.mixed.progressive.full)と.immersionStyle(selection:in:)修飾子のAPI。 

  3. Apple Developer Documentation: ornament(visibility:attachmentAnchor:contentAlignment:ornament:)。指定されたアンカーでWindowにornament UI平面を追加するSwiftUIビュー修飾子。 

  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。immersive spaceの定義と開き方のエンドツーエンドガイド。 

関連記事

RealityKit And The Spatial Mental Model

RealityKit is an entity-component-system, not SwiftUI in 3D. Anchors place entities in real space. Five ways the model d…

16 分で読める

HealthKit + SwiftUI on iOS 26: Authorization, Sample Types, and Cross-Platform Patterns

Real production patterns from Water (water tracking, HKQuantitySample) and Return (mindful sessions, HKCategorySample). …

17 分で読める

The Cleanup Layer Is the Real AI Agent Market

Charlie Labs pivoted from building agents to cleaning up after them. The AI agent market is moving from generation to pr…

15 分で読める