はじめに

こんにちは!システム開発部のYです。

WWDC2025では、visionOS 26に追加される空間ビジネスアプリの機能強化の詳細が発表されました。
本記事では、以下のセッション内容をまとめています。詳細は WWDC セッションをご参照ください。

Explore enhancements to your spatial business app / 空間ビジネスアプリの機能強化の詳細

目次

Enterprise APIs for visionOS

Enterprise APIの基本的な要件を確認します。

これらのAPIは幅広いユーティリティと高度なデバイスアクセスを提供します。
利用するには、管理対象エンタイトルメントとデベロッパアカウントに関連付けられたライセンスファイルが必要です。
これらのAPIは、企業が自社従業員向けに開発する社内アプリや、他社での内部利用を目的として開発されるカスタムアプリ向けに設計されています。

新APIの概要、既存APIの改善点

Streamline development

Wider API access

Vision Pro Developer Strapを通じて、USBビデオクラスデバイスから外部ビデオへアクセスできる機能を導入しています。
このAPIを利用することで、UVC対応Webカメラを活用したビデオ会議の強化、特殊な画像処理デバイスによるリモート診断、さらには産業用検査カメラを用いた品質管理への応用が可能になります。
また、高度なオンデバイス機械学習を実現するために、Neural Engineへのアクセスも提供しています。

最新のvisionOSでは、これらのAPIをすべての開発者が利用できるようになりました。

Object tracking workflows

昨年、visionOSにオブジェクトトラッキングが導入され、現実世界の特定のオブジェクトをアプリで認識・追跡できるパワフルな体験を実現しました。
今年は、コマンドラインから直接トレーニングを行える機能が追加されます。

xcrun createml objecttracker -s my.usdz -o my.referenceobject

モデルのトレーニングプロセスを自動化し、既存のパイプラインに統合することで、オブジェクトトラッキングアセットを効率的に管理できます。
これにより、オブジェクトごとにCreate MLアプリを手動で利用する必要はなくなります。

Improved license management

エンタープライズライセンス管理もよりシンプルになります。

Apple Developerアカウントから直接ライセンスファイルにアクセスできるようになりました。更新は自動的にワイヤレスでアプリにプッシュされます。
そして、Vision Entitlement Servicesフレームワークを作成しました。このフレームワークにより、アプリが特定の機能について適切にライセンスされ承認されているか簡単に確認できます。

Vision Entitlement Servicesにより、アプリが特定のエンタープライズAPIへアクセス可能かどうかを判断できます。
ライセンスのステータスや有効期限を確認でき、Increased Performance Headroomエンタイトルメントを利用するアプリでは、負荷の高いタスクの実行前にこの検証を行うことで、最高のパフォーマンスを確保できます。

import VisionEntitlementServices

func checkLicenseStatus() {
    // Get the shared license details instance
    let license = EnterpriseLicenseDetails.shared

    // First, you might check the overall license status
    guard license.licenseStatus == .valid else {
        print("Enterprise license is not valid: \(license.licenseStatus)")
        // Optionally disable enterprise features or alert the user
        return
    }

    // Then, check for a specific entitlement before using the feature
    if license.isApproved(for: .mainCameraAccess) {
        // Safe to proceed with using the main camera API
        print("Main Camera Access approved. Enabling feature...")
        // ... enable camera functionality ...
    } else {
        // Feature not approved for this license
        print("Main Camera Access not approved.")
        // ... keep feature disabled, potentially inform user ...
    }
}

例として、アプリがメインカメラへアクセスできるよう適切に設定されているかを確認する方法を示します。

まず、Vision Entitlement Servicesフレームワークをインポートします。
次に、Enterprise License Detailsシングルトンを利用してライセンスが有効であることを確認します。
最後に、mainCameraAccessに対してライセンスが認証されているかを確認します。

Enhance user experience

Adaptive window placement

これは、Window Followモードと言います。
有効にすると、コンテンツがアクセス可能な状態になり、ユーザーの位置に基づいて表示されます。
この動作を使用するのに必要となるのが、window-body-followエンタイトルメントです。
エンタイトルメントが付与され、アプリに組み込まれると、この動作はデバイス上のすべてのウィンドウで有効になります。

ユーザーの移動に合わせてウィンドウを移動させる事が出来ます。

Shared coordinate spaces

アプリのコンテンツがまるで物理的に存在しているかのように、誰もが自然に操作し、話し合うことができます。
SharePlayを利用することで、共有座標空間の検出、接続、セッション管理を自動的に処理する高レベルAPIが提供されます。
詳しくは「Share visionOS experiences with nearby people」をご参照ください。

一部のシナリオでは、より高度な制御が必要になる場合があります。
たとえば、カスタムネットワークインフラとの統合やデバイス間通信を直接処理する必要があるケースです。こうしたユースケースのために、新しいARKit APIであるSharedCoordinateSpaceProviderが用意されています。

このAPIにより、複数の参加者が座標系を合わせることができます。
これは、選択したローカルネットワーク上で特定のARKit生成データを交換することで実現されます。
各参加者は、このデータを他の参加者と継続的に共有します。

World TrackingやHand Trackingと同様に、インスタンス化してアクティブなARKitSession上で実行します。
実行されると、SharedCoordinateSpaceProviderはCoordinateSpaceDataオブジェクトにカプセル化された必要な配置情報を生成します。このデータを取得するには、nextCoordinateSpaceData()関数を使用します。CoordinateSpaceDataは他の参加者に送信され、共有座標空間を確立する役割を担います。

ネットワーク経由で他の参加者からCoordinateSpaceDataを受信した場合は、SharedCoordinateSpaceProviderにpush()メソッドを通じて渡します。
各受信データには、送信者の一意のparticipantIDがタグ付けされます。
最後に、プロバイダはセッションライフサイクルの管理を支援し、参加者が共有スペースから退出した場合など、重要な変更を通知するためにeventUpdates非同期シーケンスを提供します。

まず、SharedCoordinateSpaceProviderを作成してARKitSession上で実行します。
ネットワーク上の他の参加者からデータを受信した場合は、そのデータをpushし、ローカルプロバイダの認識を更新します。

デバイス間で共有する必要があるデータを取得するには、nextCoordinateSpaceData()関数を呼び出します。これにより、ローカル状態を表すCoordinateSpaceDataオブジェクトが生成され、ネットワーク上でブロードキャストできます。

最後に、processEventUpdates()がカスタム共有スペース管理の中心となり、ネットワークレイヤとARKitの座標配置を橋渡しします。

以下コード

//
//  SharedCoordinateSpaceModel.swift
//

import ARKit

class SharedCoordinateSpaceModel {
    let arkitSession = ARKitSession()
    let sharedCoordinateSpace = SharedCoordinateSpaceProvider()
    let worldTracking = WorldTrackingProvider()

    func runARKitSession() async {
        do {
            try await arkitSession.run([sharedCoordinateSpace, worldTracking])
        } catch {
            reportError("Error: running session: \(error)")
        }
    }

    // Push data received from other participants
    func pushCoordinateSpaceData(_ data: Data) {
        if let coordinateSpaceData = SharedCoordinateSpaceProvider.CoordinateSpaceData(data: data) {
            sharedCoordinateSpace.push(data: coordinateSpaceData)
        }
    }

    // Poll data to be sent to other participants
    func pollCoordinateSpaceData() async {
        if let coordinateSpaceData = sharedCoordinateSpace.nextCoordinateSpaceData {
            // Send my coordinate space data
        }
    }

    // Be notified when participants connect or disconnect from the shared coordinate space
    func processEventUpdates() async {
        let participants = [UUID]()
        for await event in sharedCoordinateSpace.eventUpdates {
            switch event {
                // Participants changed
            case .connectedParticipantIdentifiers(participants: participants):
                // handle change
                print("Handle change in participants")
            case .sharingEnabled:
                print("sharing enabled")
            case .sharingDisabled:
                print("sharing disabled")
            @unknown default:
                print("handle future events")
            }
        }
    }

    // Be notified when able to add shared world anchors
    func processSharingAvailabilityUpdates() async {
        for await sharingAvailability in worldTracking.worldAnchorSharingAvailability
            where sharingAvailability == .available {
            // Able to add anchor
        }
    }
    // Add shared world anchor
    func addWorldAnchor(at transform: simd_float4x4) async throws {
        let anchor = WorldAnchor(originFromAnchorTransform: transform, sharedWithNearbyParticipants: true)
        try await worldTracking.addAnchor(anchor)
    }

    // Process shared anchor updates from local session and from other participants
    func processWorldTrackingUpdates() async {
        for await update in worldTracking.anchorUpdates {
            switch update.event {
            case .added, .updated, .removed:
                // Handle anchor updates
                print("Handle updates to shared world anchors")
            }
        }
    }
}

Content protection

SharePlay、画面キャプチャや録画、画面ミラーリングなどの機能は非常に便利ですが、機密データを意図せず公開してしまう可能性があります。そこで、何がキャプチャされ、他人と共有されるかをコントロールできる新しいAPIが登場しました。

contentCaptureProtectedは、SwiftUIの新しいモディファイアです。
protected-contentエンタイトルメントを持つアプリでサポートされており、任意のユーザーインターフェイス要素やRealityKitシーン全体に追加するだけで利用できます。

コンテンツが保護対象の場合、システムは自動的にそのコンテンツをスクリーンキャプチャ、録画、ミラーリング、共有ビューから隠します。ただし、デバイスを装着しているユーザーにはコンテンツが完全に表示されます。

// Example implementing contentCaptureProtected

struct SecretDocumentView: View {
    var body: some View {
        VStack {
            Text("Secrets")
                .font(.largeTitle)
                .padding()

            SensitiveDataView()
                .contentCaptureProtected()
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top)
    }
}

コンテンツを保護するには、contentCaptureProtectedモディファイアを追加します。
このコンテンツを共有しようとすると、システムによってフィードは自動的に非表示になります。

Visualize the Environment

Greater camera access

APIが拡張され、左右のカメラに直接アクセスできるようになりました。
これはARKitのCameraFrameProvider APIです。さらに、カメラフィードのサポートがイマーシブ空間と共有スペースの両環境で利用可能になり、アプリが他のアプリやウィンドウと連携できるようになりました。

Camera regions

多くの作業現場では、特定の情報を監視する必要があります。
たとえば、技術者は複雑な機械の小さなゲージを読み取る必要があり、検査官は照明が不十分な環境で部品を調べなければならない場合があります。

これに対処するため、強力な新機能を導入します。
Vision Proを装着したユーザーは、現実世界のビューから特定の領域を選択し、その領域専用のビデオフィードを個別のウィンドウに表示できます。

このフィードは拡大や強調が可能で、重要な詳細を明確にすることができます。
visionKitにはCameraRegionViewという新しいSwiftUIビューが用意されており、視覚的に強調したい領域の上にこのウィンドウを配置するだけで利用できます。

CameraRegionViewは、仮想カメラに適切な領域と空間を提供します。
よりきめ細かな制御が必要な場合は、ARKitの新しいCameraRegionProvider APIを使用できます。
これは直接アクセスを可能にするもので、すでにARKitを利用している場合、アンカーの扱いに慣れている場合、または特定のUIニーズがある場合に便利です。

ゲージのビデオフィードがアプリに表示されます。

// Example Camera Regions API in SwiftUI

import SwiftUI
import VisionKit

struct InspectorView: View {
    var body: some View {
        CameraRegionView(isContrastAndVibrancyEnhancementEnabled: true)
    }
}

visionKitをインポートします。次に、CameraRegionViewを追加し、isContrastAndVibrancyEnhancementEnabledパラメータをtrueで初期化することで、手ぶれ補正を有効にし、コントラストとバイブランスを高めます。

// Example Camera Regions API in SwiftUI

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

        WindowGroup(id: "InspectorView") {
            InspectorView()
        }
        .windowResizability(.contentSize)
    }
}

InspectorView用に2つ目のWindowGroupを作成します。
より複雑なアプリでは、CameraRegionViewでクロージャを利用できます。
このクロージャを使って、カメラ画像を分析する処理をコードに追加します。

struct InspectorView: View {
    @Environment(CameraFeedDelivery.self) private var cameraFeedDelivery: CameraFeedDelivery

    var body: some View {
        CameraRegionView(isContrastAndVibrancyEnhancementEnabled: true) { result in
            var pixelBuffer: CVReadOnlyPixelBuffer?
            switch result {
            case .success(let value):
                pixelBuffer = value.pixelBuffer
            case .failure(let error):
                reportError("Failure: \(error.localizedDescription)")
                cameraFeedDelivery.stopFeed()
                return nil
            }

            cameraFeedDelivery.frameUpdate(pixelBuffer: pixelBuffer!)
            return nil
        }
    }
}

CameraRegionViewをクロージャを受け入れる形に変更し、カメラフレームが到着するたびに処理できるようにします。
まず、カメラフレームをキャプチャするためにcameraFeedDeliveryクラスを追加します。

クロージャ内ではCameraRegionViewのpixelBufferを使用します。
エラーをチェックしたうえで、pixelBufferをcameraFeedDeliveryクラスに渡します。
このクロージャはnilを返しますが、これはpixelBufferを変更していないことを意味します。
もし修正したpixelBufferを返した場合、CameraRegionViewは調整後のカメラ画像をレンダリングします。

次に、ARKitのカメラ領域APIを見てみましょう。
多くのアプリでは、特定の3Dオブジェクトにカメラ領域を関連付けたいケースがあります。
環境内の特定のオブジェクトを認識した後、その位置にカメラ領域を配置したい場合もあるでしょう。アンカーと3Dオブジェクトをきめ細かく制御する必要がある場合、このAPIは低レベルのプリミティブを提供し、アンカーを定義する必要があります。

ARKitには、CameraRegionProviderという新しいタイプのデータプロバイダがあります。
まず、他のARKit機能と同様に、ARKitSession上でデータプロバイダを実行します。

プロバイダが稼働したら、次のステップとしてカメラ領域の範囲を正確に指定します。
そのためにCameraRegionAnchorを作成し、プロバイダに追加します。
これらのアンカーの役割は、仮想カメラを設置したい現実世界の正確な領域を指定することです。
ARKitが実行されると、プロバイダはアンカーに更新を送信します。
各更新には新しいpixelBufferが含まれており、そのバッファには特定の空間領域の安定したビューが保持されています。

// Camera regions in ARKit

let anchor = CameraRegionAnchor(originFromAnchorTransform: transform,
                                width: width,
                                height: height,
                                cameraEnhancement: .contrastAndVibrancyEnhancement)

do {
    try await provider.addAnchor(anchor)
} catch {
    reportError("Error adding anchor: \(error)")
}

CameraRegionAnchorの作成方法を見ていきましょう。
まず、標準的な6自由度変換を使用して、ワールド内でのアンカーの位置と向きを定義します。
次に、その物理サイズとして幅と高さをメートル単位で指定します。
これらのパラメータを組み合わせることで、カメラ領域の現実世界におけるウィンドウを定義します。さらに、ウィンドウのコントラストを向上させるのか、あるいは単に安定化させるのかをARKitに指示する必要があります。最後に、これをCameraRegionProviderに追加します。

// Camera regions in ARKit

Task {
    let updates = provider.anchorUpdates(forID: newAnchor.id)
    for await update in updates {
        let pixelBuffer = update.anchor.pixelBuffer
        // handle pixelBuffer
    }
}

アンカーを追加した後、anchorUpdates(forID:)を呼び出し、newAnchorのアンカーIDを渡します。
これにより、アンカーで指定した位置に正確に表示され、コード内で更新ごとに提供されるpixelBufferを処理できます。

まとめ

visionOS 26に追加される空間ビジネスアプリの機能強化の詳細についてまとめました。
今回のアップデートにより、エンタープライズ向けのAPIやカメラ制御が大幅に拡張され、より実務に直結したアプリ開発が可能になった印象です。
この記事が少しでも参考になれば幸いです。詳細はWWDCのセッションをご参照ください。

参考



ギャップロを運営しているアップフロンティア株式会社では、一緒に働いてくれる仲間を随時、募集しています。 興味がある!一緒に働いてみたい!という方は下記よりご応募お待ちしております。
採用情報をみる