Claude CodeでvisionOSアプリを開発する手順
Claude CodeでvisionOSアプリを作る際の、ウィンドウ・ボリューム・イマーシブ空間の実装手順とXcodeとの役割分担を、型検査で確かめたコード例で解説します。
visionOSはApple Vision Pro向けのオペレーティングシステムです。ウィンドウ・ボリューム・イマーシブ空間という3種類のシーンをSwiftUIで組み合わせてアプリを作ります。Claude CodeはこれらのSwift・SwiftUI・RealityKitのコードを書き換え、ターミナルでの検査を担当します。プロジェクトの作成とSimulatorでの表示確認は、引き続きXcodeのGUI操作として残ります。
visionOS開発を始める前に確認すること
visionOS向けの開発にはApple Silicon搭載のMacが必要です。加えてXcode本体とvisionOS SDKが要ります。既存のiPhone・iPadアプリがあるなら、SwiftUIの資産をvisionOSへ持ち込む道もあります。
新規プロジェクトはXcodeの「File > New > Project」から、visionOSセクションのAppテンプレートを選んで作ります。設定ダイアログで初期シーンの種類を選びます。2Dコンテンツが中心ならWindow、3Dコンテンツが中心ならVolumeです。イマーシブシーンもここから追加できます。3Dアセットを扱うなら、Reality Composer Proのプロジェクトファイルを同時に含めておくと便利です。プリミティブ形状やUSDZアセットから3Dシーンを組み立て、RealityKitのアニメーションや挙動も試せます。
3Dコンテンツ自体はRealityKitとReality Composer Proで構築し、RealityViewで表示します。RealityKitは照明条件に合わせて影を落とせる3Dレンダリングエンジンで、シェーダーの業界標準規格MaterialXにも対応しています。
周囲の物理空間と統合したいときはARKitを使います。Plane Estimation・Scene Reconstruction・Image Anchoring・World Tracking・Skeletal Hand Trackingといった機能が使えます。ただし使えるのは、アプリがShared SpaceからFull Spaceへ移り、ユーザーに許可を求めた後です。
Claude CodeとXcodeの役割分担
Claude Codeはファイルの読み書きとターミナル操作を軸にしたコーディングエージェントです。コードの編集とコマンドラインの検査がClaude Code、GUIでの作成と目視確認がXcodeという境目を先に決めておくと、指示に迷いが減ります。具体的な振り分けは次の表のとおりです。
どちらが受け持つか
Claude Code
- WindowGroup・ImmersiveSpaceなどのシーン宣言の編集
- USDZアセットを読み込むコードの実装
- コンパイラやxcodebuildによるコマンドラインでの検査
Xcode(GUI)
- Xcodeプロジェクトの新規作成
- Simulatorでの表示・ジェスチャー確認
- Reality Composer Proでの3Dシーン作成
XcodeにClaudeを直接組み込む方法としてはClaude for Xcodeの使い方もあります。ただしこれはApple自身がXcodeの「Intelligence」設定に組み込んだ別の機能で、導入はXcodeの設定画面から行います。
書かせたコードを型検査で確かめる
Claude Codeが書いたSwiftは、Xcodeを開く前にターミナルで型検査にかけられます。1ファイルだけなら、visionOS SDKを指定したコンパイラで足ります。Claude Code v2.1.289、Xcode 26.6、visionOS SDK 26.5の環境での実行例です。
claude --version
xcodebuild -showsdks | grep -i vision
xcrun swiftc -parse-as-library -typecheck \
-sdk "$(xcrun --sdk xros --show-sdk-path)" \
-target arm64-apple-xros2.0 Scene.swift2.1.289 (Claude Code)
visionOS SDKs:
visionOS 26.5 -sdk xros26.5
visionOS Simulator SDKs:
Simulator - visionOS 26.5 -sdk xrsimulator26.5型検査が通ると、コンパイラは何も出さずに終わります。-parse-as-libraryを付けないと、@mainを持つファイルは「top-level codeを含むモジュールには使えない」というエラーで止まります。
この検査では、4種類の失敗が出ます。どれもClaude Codeにエラー全文を読ませれば直せる種類です。
| 書いたコード | コンパイラの出力 | 直し方 |
|---|---|---|
openImmersiveSpace(id:)にawaitが無い | コンパイラの出力expression is 'async' but is not marked with 'await' | 直し方awaitを付ける |
.windowStyle(style: .volumetric) | コンパイラの出力extraneous argument label 'style:' in call | 直し方ラベルを外して.windowStyle(.volumetric) |
Model3Dをimport RealityKitなしで使う | コンパイラの出力cannot find 'Model3D' in scope | 直し方import RealityKitを足す |
SwiftUIとRealityKitを両方importしてsome Scene | コンパイラの出力'Scene' is ambiguous for type lookup in this context | 直し方some SwiftUI.Sceneと書く |
四つ目は見落としやすい型です。RealityKitにもSceneという型があるため、両方をimportしたファイルではSwiftUI.Sceneと修飾しないと、Appへの準拠まで連鎖して失敗します。
検査の3段階
- 1
コンパイラで型検査する
awaitの抜け・引数ラベル・名前の衝突は、ここで落ちます。 - 2
Simulatorで動かす
型検査は通っても、実行して初めて分かる失敗があります。複数シーンの同時表示やイマーシブ空間の二重オープンは、実行時にログへエラーが出る種類です。
- 3
Instrumentsで描画を測る
表示が重いときは、RealityKit Traceテンプレートで測ります。測定の手順は後のつまずきの節にあります。
ウィンドウを実装する
visionOSアプリはWindowGroupから始まります。次のコードは、ウィンドウの背景に半透明の黒を敷きつつ、イマーシブ空間を1つ宣言する最小構成です。
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.background(.black.opacity(0.8))
}
ImmersiveSpace(id: "Immersive") {
ImmersiveView()
}
}
}visionOSでは、アプリを最初に起動したとき、システムがウィンドウを見ている先に置きます。起動後に開く新しいウィンドウは、既存ウィンドウの手前に少しずつずらして置かれます。この配置を変えたいときはdefaultWindowPlacementを使います。次の例は、contentウィンドウの右端に別のウィンドウを置きます。
WindowGroup(id: "content") {
ContentView()
}
WindowGroup(id: "trailingWindow") {
TrailingWindowView()
}
.defaultWindowPlacement { content, context in
guard let contentWindow = context.windows.first(where: { $0.id == "content" }) else {
return WindowPlacement(nil)
}
return WindowPlacement(.trailing(contentWindow))
}画面の下端から140ポイントのような座標で置く例が公式ドキュメントにありますが、これはmacOS向けです。visionOS SDKで型検査すると、context.defaultDisplayが「unavailable in visionOS」と報告されます。座標を渡すWindowPlacementの初期化子も「'init(_:size:)' is unavailable in visionOS」です。macOSのサンプルをそのまま移植させると、この種のエラーで止まります。
ウィンドウが表示された後は、位置やサイズをアプリ側から直接操作できません。システムは、人が自分の作業空間を自由に動かせる状態を優先しています。指定したサイズが許容範囲の外にあると、システムはその次元を範囲内に丸めます。状態の復元時には、前回の位置とサイズに戻ります。
ボリュームで3Dコンテンツを追加する
3Dコンテンツが主役のシーンにはボリュームを使います。WindowGroupに.windowStyle(.volumetric)を指定するだけで、そのシーンは3D空間として描画されます。
import SwiftUI
import RealityKit
@main
struct MyApp: App {
var body: some SwiftUI.Scene {
WindowGroup {
Model3D(named: "balloons")
}.windowStyle(.volumetric)
}
}Model3Dはimport RealityKitが必要で、some Sceneはsome SwiftUI.Sceneと書いています。理由は前の節の表のとおりです。
ボリュームのサイズは、ポイントではなくメートル単位で指定するのが公式の推奨です。次の例は1辺1メートルの立方体を確保します。
WindowGroup(id: "globe") {
Globe()
}
.windowStyle(.volumetric)
.defaultSize(width: 1, height: 1, depth: 1, in: .meters)ボリュームは通常のウィンドウと違って動的スケーリングをせず、固定スケールで描画されます。そのためUIというより物理的なオブジェクトとして人の前に現れます。RealityViewを組み合わせれば、RealityKitで構築した3Dシーンをボリューム内にそのまま表示できます。
人の視線とジェスチャーにどう対応するか
visionOSでの入力は、マウスやタッチとは前提が異なります。公式ドキュメントは3種類の入力経路を挙げています。
3つの入力経路
間接入力
視線で対象を示し、親指と人差し指を触れ合わせて操作を始めます。その後の指と手の動きがジェスチャーの種類を決めます。
直接入力
指が画面上の項目と同じ位置に来たときに、システムが操作を認識します。
キーボード入力
接続したマウス・トラックパッド・キーボードで、項目の操作・メニューコマンド・ジェスチャーを実行します。
多くのSwiftUIビューは、操作時に動かすコードを渡すだけで反応します。ビューにジェスチャー認識を足せば、タップ・長押し・ドラッグ・回転・ズームも扱えます。
イマーシブ空間を実装する
人の周囲を部分的、あるいは完全にコンテンツで覆いたいときはイマーシブ空間を使います。ImmersiveSpaceシーンを宣言し、openImmersiveSpaceアクションで開きます。
struct NewSpaceButton: View {
@Environment(\.openImmersiveSpace) private var openImmersiveSpace
var body: some View {
Button("View Orbits") {
Task {
await openImmersiveSpace(id: "orbits")
}
}
}
}イマーシブ空間には3種類のスタイルがあります。
| スタイル | 挙動 |
|---|---|
| mixed(既定) | 挙動周囲のパススルー映像を残しつつ仮想コンテンツを重ねる |
| full | 挙動パススルーを完全に隠し、アプリのコンテンツだけを表示する |
| progressive | 挙動パススルーを隠す度合いを人が段階的に調整できる |
スタイルは、ウィンドウと仮想コンテンツの前後関係にも影響します。mixedでは、仮想オブジェクトがウィンドウを隠すことも、ウィンドウが仮想オブジェクトを隠すこともあります。fullとprogressiveでは、ウィンドウは常に仮想コンテンツの手前に描画されます。パススルーが消えたときにウィンドウを見失わないための設計です。
完全イマーシブ(full)で開いたときは、安全境界もあります。頭の初期位置からおよそ1.5メートルの範囲を出ると、システムが没入体験を自動で止めます。
スタイルを切り替え可能にするには、immersionStyle(selection:in:)モディファイアで候補を渡します。
@main
struct MyImmersiveApp: App {
@State private var currentStyle: ImmersionStyle = .full
var body: some Scene {
WindowGroup() {
ContentView()
}
ImmersiveSpace(id: "solarSystem") {
SolarSystemView()
}.immersionStyle(selection: $currentStyle, in: .full)
}
}空間を開いた後にスタイルを変えられるのは、第2引数に渡した候補の中からだけです。
アプリ起動時にいきなりイマーシブ空間を開きたい場合は、Info.plistのUIApplicationPreferredDefaultSceneSessionRoleキーにUISceneSessionRoleImmersiveSpaceApplicationを設定します。このとき、visionOSは宣言順で最初に見つかったイマーシブ空間を開きます。起動時のスタイルを指定するなら、シーン構成を足してUISceneInitialImmersionStyleキーにmixed・full・progressiveのいずれかを渡します。起動直後に人を圧倒しないよう、公式はデザインガイドラインの参照を促しています。
ウィンドウと空間の遷移を正しい順序で書く
ウィンドウもイマーシブ空間も、アプリに残る最後の1つのシーンとしてはプログラムから閉じられません。開始ボタンを押したらウィンドウを閉じてイマーシブ空間へ切り替える、というよくあるパターンには順序の注意が要ります。
Button("Start") {
Task {
await openImmersiveSpace(id: "chessboard")
dismissWindow(id: "start") // Runs after the space opens.
}
}openImmersiveSpaceは非同期に実行されます。空間が開き終わる前は、そのウィンドウがアプリの最後のシーンです。dismissWindowが先に走ると、最後のシーンを閉じる操作としてシステムに無視される恐れがあります。
この遷移では、awaitの抜けはコンパイラが止めてくれます。一方でdismissWindowを置く位置は、間違えても型検査を通ります。Claude Codeへの指示には、awaitで完了を待ってから閉じる処理を置く順序を明記しておくと確実です。
シーンタイプの使い分け早見表
| シーン | 向くコンテンツ | 特徴 |
|---|---|---|
| Window | 向くコンテンツ2Dが中心のUI | 特徴従来のビュー・コントロールをそのまま使える |
| Volume | 向くコンテンツ3Dが中心の単一オブジェクト | 特徴メートル単位の固定スケールで描画される |
| Immersive Space | 向くコンテンツ周囲を覆う没入体験 | 特徴mixed・full・progressiveの3スタイル、同時に1つのみ |
よくあるつまずき
複数のウィンドウを開こうとしても何も起きない
visionOSは既定では複数シーンの同時表示を許可しません。openWindowを呼んでも何も起きないときは、Info.plistのUIApplicationSupportsMultipleScenesキーがtrueになっているか確認します。falseのままだとSwiftUIはアクションを無視し、ランタイムエラーだけをログに残します。このキーは、ImmersiveSpaceを含む複数シーンを同時に表示する条件でもあります。
環境値supportsMultipleWindowsを読めば、実行時に複数ウィンドウが使えるかを判定できます。macOSでは常にtrueで、iPadOSとvisionOSでは上のキーがtrueのときだけtrueです。
Unityで3Dコンテンツを作る場合の選択肢
ボリュームはRealityKitだけでなくUnityでも作れます。既存のUnityプロジェクトをvisionOS向けに持ち込む場合は、Unity公式MCPサーバーをClaude Codeで使う方法で扱っているMCP連携が使えます。
Claude Codeにプロジェクト作成そのものは頼めない
Xcodeのテンプレート選択(File > New > Project)はGUI操作で完結する工程です。プロジェクトを作成した後、SwiftのコードやInfo.plistの編集をClaude Codeに任せる順序で進めます。Claude Code自体のインストール手順はClaude Codeのインストールガイドにまとめています。
描画が重いと感じたらInstrumentsで確認する
Apple Vision Proは、新しいフレームを90FPSで描画しようとします。締め切りに間に合わないとフレームが落ち、空間体験の質が下がります。ボトルネックを探すには、Xcodeで「Product > Profile」を選び、RealityKit Traceテンプレートで計測します。
計測の対象は実機でもSimulatorでも選べますが、時間に関する値は実機で見る必要があります。Mac上のSimulatorとデバイスはソフトウェアも性能も違うためです。Simulatorは、時間に依存しない性能面の素早い見直しに向きます。結果をClaude Codeに渡すときは、RealityKit Framesで赤(ドロップ)になったフレームの範囲まで書くと、3Dコンテンツの複雑さを調整する相談に使えます。
まとめ
公式のコード例は、そのまま貼らずに型検査へ通してから使うのが安全です。macOS向けの値が混ざっていたり、名前の衝突で失敗したりするためです。Claude Codeに任せるのはコードの編集と型検査まで。実行時の挙動はSimulatorで、描画性能は実機で確かめます。
参照した一次ソース
この記事の記述は、次の一次ソースと照合しています。更新時に参照先が変わった場合は一覧も更新します。
- developer.apple.com/visionos
- developer.apple.com/documentation/visionos
- developer.apple.com/documentation/visionos/creating-your-first-visionos-app
- developer.apple.com/documentation/visionos/presenting-windows-and-spaces
- developer.apple.com/documentation/visionos/positioning-and-sizing-windows
- developer.apple.com/documentation/visionos/creating-fully-immersive-experiences
- developer.apple.com/documentation/visionos/adding-3d-content-to-your-app
- developer.apple.com/documentation/swiftui/immersivespace
- developer.apple.com/documentation/visionOS/analyzing-the-performance-of-your-v…