Apple

visionOS TC 2025 「新しいマイホームで過ごすApple Vision Proとの新生活」

はじめに

visionOS TC 2025で「新しいマイホームで過ごすApple Vision Proとの新生活」というタイトルでLT登壇しました!

「Apple Vision Pro、買ったはいいけど高価な文鎮になってない?」

…正直、私も約60万円もする高価なデバイスをフルに活用できていないと思います
せっかく買ったのに使い道がない…なんてもったいないですよね。

でも、新居に引っ越したタイミングで「せっかくだから日常生活で使ってみよう」と思い立ち、いろいろ試行錯誤してみました

このセッションでは、僕が実際に新居で試してみたApple Vision Proの活用事例について、色々試行した結果を紹介しました。「こんな使い方もあるんだ!」と思ってもらえたら嬉しいです。

本記事では、登壇で紹介した各機能の技術的な実装について詳しく解説していきます。

サンプルコードは以下のリポジトリで公開しています。
https://github.com/u5-03/VisionOSTC

発表内容

今回の発表では、以下の内容を紹介しました。

  1. Widgetでの情報の確認
  2. Portalでの非日常体験
  3. 配置シミュレーション
  4. ハンドジェスチャーによる操作
  5. 手が汚れる作業中のデバイス操作
  6. Push通知による連絡
  7. Siriショートカットでの操作

それぞれの実装について見ていきましょう!

Widgetでの情報の確認

visionOS26からWidgetKitがサポートされ、Apple Vision Pro上にWidgetを表示できるようになりました。
基本実装はiOSやmacOSのWidgetと基本的には同じなんですが、機能や体験は異なり、visionOSでのWidgetは壁やテーブルの上などの平面に固定させることができます。
標準で用意されている天気アプリなどのWidgetはもちろん、カスタムで実装すればアプリを開かなくても、任意の情報を常に生活に溶け込むように表示させることができます。

Portalでの非日常体験

RealityKitのPortalMaterialを使うと、現実空間に「窓」を作って、その先に別の世界を表示できます。
どこでもドアのように、空間に穴が開いて別の場所が見えるような体験を実現することができます。

活用例としては、旅行先で撮った空間写真をPortal越しに表示することが考えられます。
リビングの壁に「窓」があって、そこから沖縄の海が見える…みたいな体験ができます。

Portalの仕組み

Portalを作るには、3つの要素を組み合わせます。

  1. WorldComponent: Portal内の「別世界」を定義するコンポーネントです。このコンポーネントを付けたエンティティは、Portalを通してのみ表示されるようになります。つまり、普通に空間を見ても見えないけど、Portalの「窓」を通すと見える、という状態を作れます
  2. PortalMaterial: 平面を「窓」として機能させるマテリアルです。このマテリアルを適用した平面が、別世界への入口になります。マテリアル自体は透明で、その先にあるWorldComponentの世界が見えるようになります
  3. PortalComponent: Portal平面と別世界を接続するコンポーネントです。「この窓からはこの世界が見える」という関連付けを行います

Portal実装の基本構造

それでは実際のコードを見ていきましょう!

private func createPortalEntity() -> Entity {
    // まず、Portal全体を管理するルートエンティティを作成
    let portalRoot = Entity()
    portalRoot.name = "portalRoot"

    // ========================================
    // Step 1: Portal内部の「別世界」を作成
    // ========================================
    let portalWorld = Entity()
    portalWorld.name = "portalWorld"

    // WorldComponentを追加!
    // これを付けることで、このエンティティ(とその子要素)は
    // Portalを通してのみ見えるようになります
    portalWorld.components.set(WorldComponent())

    // ========================================
    // Step 2: Portal入口(窓)となる平面を作成
    // ========================================
    // generatePlane(width:, height:)で平面メッシュを生成
    // デフォルトではXY平面(正面向き)が生成されます
    let portalMesh = MeshResource.generatePlane(
        width: portalSize,
        height: portalSize
    )

    // PortalMaterialを適用
    // このマテリアルを適用した平面が「窓」として機能します
    let portalMaterial = PortalMaterial()
    let portalPlane = ModelEntity(mesh: portalMesh, materials: [portalMaterial])
    portalPlane.name = "portalPlane"

    // ========================================
    // Step 3: Portal平面とWorldを接続
    // ========================================
    // PortalComponentでターゲットのWorldを指定
    portalPlane.components.set(PortalComponent(target: portalWorld))

    // ルートエンティティに追加
    portalRoot.addChild(portalWorld)
    portalRoot.addChild(portalPlane)

    return portalRoot
}

空間写真の表示

Portal内に何を表示するかは自由ですが、今回は空間写真を表示してみました。Apple Vision Proで撮影した空間写真(Spatial Photo)は、左右の目に異なる画像を表示することで立体視を実現しています。

この空間写真をRealityKitで表示するには、ImagePresentationComponentを使います。
desiredViewingMode.spatialStereoに設定にしないと、立体視が有効にならず普通の2D画像として表示されます。

private func createPhotoEnvironment(on root: Entity) async throws {
    // ========================================
    // Step 1: 空間写真ファイルを読み込む
    // ========================================
    guard let imageURL = Bundle.main.url(forResource: "space_picture", withExtension: "heic") else {
        print("エラー: space_picture.heic が見つかりません")
        throw NSError(domain: "PortalError", code: 1,
                      userInfo: [NSLocalizedDescriptionKey: "Failed to load space_picture.heic"])
    }

    // ========================================
    // Step 2: 空間写真を表示するエンティティを作成
    // ========================================
    let imageEntity = Entity()
    imageEntity.name = "spatialPhoto"

    // ImagePresentationComponentを作成
    var presentationComponent = try await ImagePresentationComponent(contentsOf: imageURL)

    // ========================================
    // Step 3: 立体視モードを設定(超重要!!)
    // ========================================
    if presentationComponent.availableViewingModes.contains(.spatialStereo) {
        // これを設定しないと、ただの2D画像として表示されてしまいます!
        presentationComponent.desiredViewingMode = .spatialStereo
    }

    imageEntity.components.set(presentationComponent)

    // ========================================
    // Step 4: 位置とサイズを調整
    // ========================================
    imageEntity.position = [0, 0, -0.6]  // 60cm奥に配置
    imageEntity.scale = [1.5, 1.5, 1.5]

    root.addChild(imageEntity)
}

実装のポイントまとめ

空間写真をPortal内に表示する際のポイントをまとめておきます。

  1. desiredViewingModeの設定は必須!: .spatialStereoを設定しないと、せっかくの空間写真が普通の2D画像として表示されてしまいます。これ、本当によくあるハマりポイントです
  2. 位置の調整: Portal平面からの距離を適切に設定しましょう。近すぎると圧迫感がありますし、遠すぎると小さく見えてしまいます。僕は60cm(0.6メートル)くらいがちょうどいいと感じました
  3. スケールの調整: ImagePresentationComponentは自身でサイズを管理しますが、Portal内での見え方を調整するためにスケールを変更することもできます
  4. WorldComponentの子として追加: 空間写真のエンティティは、必ずWorldComponentが付いたエンティティの子として追加してください

配置シミュレーション

RealityKitを使うと、空間にオブジェクトを置いた時のシミュレーションをできますが、visionOSだとそのシミュレーションがよりリアルに、正確にできるので、一番向いているプラットフォームだと思います。
すでに色々シミュレーションをするアプリやサービスがありますが、自分は以前家具の配置シミュレーションができるSedierというアプリの開発に副業で関わっていました。

これはusdzの家具のデータを空間に配置して、実際の部屋の中でどのように見えるかを確認することができます。
指定のテクスチャも設定でき、様々なパターンのシミュレーションができます。

usdzのデータの準備が大変ですが、このように空間上にオブジェクトを配置したシミュレーションはvisionOSの強みを活かせると思います。

ハンドジェスチャーによる操作

visionOSでは任意の手の形をした時に任意の処理を実行させることができます。
このカスタムジェスチャーを活用すると、例えば、親指と人差し指でつまむジェスチャーで照明のON/OFFを切り替えたりなどの操作を実現することもできます。
もちろん制約もありますが、色々面白い物が作れると思います。
この実装についてはiOSDC Japan2025で発表しているので、詳細は以下の記事を参照してください!

iOSDC2025が終わったぞー!!iOSDC2025が終わったので、Iwillblogします!...

手が汚れる作業中のデバイス操作

これはvisionOSの特定の機能や実装を活用したものではないですが、visionOSの特徴として、iPhoneのように物理ディスプレイを触ることなく、アプリの操作が可能なので、手が汚れた状態でも操作が可能です。

私はよく料理をするので、料理中など手が汚れている時に、例えば魚を捌く時に手が汚れたままでも、ブラウザなどを操作して、その魚の捌き方や調理方法を調べたり、BGMを変更したりが可能です。

またマイホームの外構にはちょっとした花壇エリアがあるのですが、その時も土で手が汚れた状態でも手順などを調べながら作業をできます。
今までだったらスマホで調べるときは、①軍手を外す、②手を洗う、などをしないとスマホを触ることはできませんでしたが、それをせずに作業と調べごとを両立できるのは便利だなと思いました笑

なお軍手をつけたままでもアプリの操作は可能です笑

ちなみに調理用や掃除用の黒と白のゴム手袋でも操作はできました!

Push通知による連絡

アプリを開いていない状態で情報を確認したり、何か操作をしたりしたい時があります。
現状visionOSで実践できる方法のうち、Push通知もそのような方法で活用できるかもしれません。

visionOSでもiOSと同じようにPush通知を受信できます。
Push通知を送れば、受信側はアプリを開かずに連絡が来たことを確認することができます。

ただvisionOSでのPush通知のUIは、以下のキャプチャのようにアイコンが表示されるに留まります。
iOSなどの他のプラットフォームのように、通知のメッセージは直接表示されないので、タップして詳細を確認する必要があります。
そのため通知が来たことを伝える手段としては使えますが、通知内容まで一緒に伝える方法としては活用しにくいかなと思います。

Siriショートカットでの操作

最後は音声操作です!AppIntentsフレームワークを使うと、Siriから直接アプリ内で指定した処理を実行できます。

AirPodsをつけた状態を除くと、Apple Vision Proは常に頭に装着しているデバイスで両手がふさがっている時にも一番「Hey, Siri !」と呼びやすいデバイスだと思います。
そのためSiriショートカットも生かしやすいデバイスだと思います。

以下は発表スライドにも掲載したSiriでサンプルアプリの処理を実行した時のサンプルです。
実際に家電の操作や連絡の処理はしていないですが、実行履歴を残す処理をしているので、Siriで操作した後にアプリ内にその操作履歴が表示されるようになっています。

AppIntentsの仕組み

AppIntentsフレームワークは、iOS 16で導入された比較的新しいAPIです。昔のSiriKitと比べると、だいぶシンプルに書けるようになりました。

主なメリットは以下の通りです:

  1. Swiftの構造体で定義できる: 宣言的に書けるので、コードが読みやすい
  2. 型安全: パラメータの型チェックがコンパイル時に行われる
  3. 自動的にSiriで使える: 特別な設定なしで、Siriから呼び出せるようになる
  4. ショートカットアプリにも対応: ユーザーが自分でショートカットを作成することも可能
  5. パラメーターの指定も可能: 例えばエアコンの温度を指定したい場合、温度の数字などのパラメータも指定可能

AppIntentの実装

各操作に対して、それぞれAppIntentを定義していきます。

struct TurnOnAirConditionerIntent: AppIntent {
    static var title: LocalizedStringResource = "エアコンをつける"
    static var description = IntentDescription("エアコンの電源をONにします")
    static var openAppWhenRun: Bool = false

    @MainActor
    func perform() async throws -> some IntentResult & ProvidesDialog {
        let message = "エアコンの電源をONにしました"

        SmartHomeManager.shared.recordAction(
            type: "エアコン",
            action: "turn_on",
            details: message
        )

        return .result(dialog: IntentDialog(stringLiteral: message))
    }
}

AppShortcutsProviderの設定

Intentを定義しただけでは、Siriから呼び出せません。AppShortcutsProviderを使って、どんなフレーズで呼び出せるようにするかを定義します。

ここで重要なのは、フレーズに必ず\(.applicationName)を含めることです。

struct SmartHomeShortcuts: AppShortcutsProvider {
    @AppShortcutsBuilder
    static var appShortcuts: [AppShortcut] {
        AppShortcut(
            intent: TurnOnAirConditionerIntent(),
            phrases: [
                "\(.applicationName)でエアコンをつけて",
                "\(.applicationName)でエアコンオン",
                "\(.applicationName)でエアコンの電源を入れて"
            ],
            shortTitle: "エアコンをつける",
            systemImageName: "power"
        )
    }
}
このキーワードはアプリ名が一番衝突を避けやすいですが、他のワードも設定可能です。
例えば連絡ができるアプリにこのSiriショートカットを実装した場合、「Aさんに連絡して」といった指示を出した場合、OS標準のMessageアプリなど他のメッセージアプリも含めて、どれを使えばいいのかOSが判断できない時があるので、その時はOS側がどのアプリで操作を進めるかをアプリのリストを提示してくれるので、実行したいアプリを選ぶことになります。

Info.plistの設定

Siriでアプリ名を認識させるには、INAlternativeAppNamesを設定します。

注意点: INAlternativeAppNames最大3つまでしか設定できません。

<key>NSSiriUsageDescription</key>
<string>このアプリはSiriでスマートホーム機能を操作するために使用します</string>

<key>INAlternativeAppNames</key>
<array>
    <dict>
        <key>INAlternativeAppName</key>
        <string>ビジョン</string>
        <key>INAlternativeAppNamePronunciationHint</key>
        <string>びじょん</string>
    </dict>
    <dict>
        <key>INAlternativeAppName</key>
        <string>スマートホーム</string>
        <key>INAlternativeAppNamePronunciationHint</key>
        <string>すまーとほーむ</string>
    </dict>
</array>

Entitlementsの設定

Siri機能を使うには、entitlementsファイルでcom.apple.developer.siriを有効にする必要があります。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>com.apple.developer.siri</key>
    <true/>
</dict>
</plist>

動作確認の手順

Siriショートカットが正しく動作するか確認するには、以下の手順で行います。

  1. アプリをビルドしてデバイスにインストール – シミュレータでは動作しないことがあるので、実機推奨
  2. アプリを一度起動する – 起動することで、AppIntentsがシステムに登録されます
  3. Siriを起動して話しかける – 「Hey Siri、ビジョンでエアコンをつけて」
  4. うまくいかない場合は… – 初回は認識されないことがあります。何度か試すとSiriが学習して、認識精度が上がります

まとめ

今回のセッションでは、新居でのApple Vision Pro活用事例として、色々と試行錯誤した結果をまとめてみました。

「Apple Vision Pro、買ったはいいけど使い道がない…」と思っている方は、今回の内容を参考に、個人開発も含めて、色々な活用方法を探ってみてください!

visionOSはまだまだ発展途上のプラットフォームです。
今後もOSのアップデートで新しい機能が追加されていくと思うので、引き続き日常での活用方法を探っていきたいと思います。

サンプルコードは以下のリポジトリで公開しています。参考にしてみてください!
https://github.com/u5-03/VisionOSTC

0

COMMENT

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA