WWDC Quick Look 💓 By SwiftGGTeam
Meet UIKit for spatial computing

Meet UIKit for spatial computing

元の動画を見る

ハイライト

UIKit App は書き直さなくても visionOS 上で動作できます。新しいターゲットを追加し、利用できない API に対応し、セマンティックカラーと hover エフェクトでプラットフォームの雰囲気に合わせ、さらに Ornament と RealityKit でコンテンツを 3D 空間へ広げます。

主な内容

ステップ 1: iPad App を visionOS へ移す

UIKit で作った iPad App を visionOS で動かしたい場合、最初の手順は Xcode の General タブで新しい実行先を追加することです。

visionOS の App アイコンも特徴的です。3 枚の画像を重ねて構成され、ユーザーがアイコンを見つめると動的な反応が生まれます。このレイヤー化されたアイコン一式を Asset Catalog に追加します。

02:01

ターゲットを追加してそのままビルドすると、エラーに遭遇する場合があります。visionOS は新しいプラットフォームであり、一部の API は適用できません。

利用できない API に対応する

visionOS は iOS 14 より前に非推奨になった API をサポートしません。また、プラットフォーム特性の違いにより、いくつかの API は利用不可としてマークされています。

  • UIDeviceOrientation: デバイスが回転できるという前提のため、visionOS には適しません
  • UIScreen: 単一画面を表す概念がこのプラットフォームでは成り立ちません
  • UITabBar の leadingAccessoryView/trailingAccessoryView: Tab Bar のレイアウト方向が異なります

03:22

例のピクセルアート App では UIPencilInteraction を使っていますが、visionOS は Apple Pencil をサポートしないため、この API は利用できません。解決策は条件付きコンパイルで除外することです。

#if !os(xrOS)
// Apple Pencil interaction code
let pencilInteraction = UIPencilInteraction()
pencilInteraction.delegate = self
view.addInteraction(pencilInteraction)
#endif

ビルドエラーを修正すると、App は Shared Space で動作できるようになります。ガラス素材の背景や hover エフェクトはシステムが標準で提供します。

セマンティックカラーとマテリアル: App をプラットフォームになじませる

iPad では白い背景に黒いタイトル文字がきれいに見えても、visionOS のガラス背景上では浮いて見えることがあります。

このプラットフォームではセマンティックカラーが特に重要です。UIColor.labelUIColor.secondaryLabel などのシステムカラーは、プラットフォーム、外観、アクセシビリティ設定に自動で適応します。UILabel がセマンティックカラーを使うと、標準で vibrancy エフェクトも得られます。

06:58

Hover エフェクト: インタラクションを知覚しやすくする

visionOS では、ユーザーが要素を見つめると hover エフェクトが発生します。システムコントロールには標準で hover エフェクトがあり、カスタムビューにも追加できます。

UIView には hoverStyle プロパティが追加されました。.highlight または .lift の効果を設定でき、UIShape API で形状をカスタマイズすることもできます。nil に設定すると hover エフェクトを削除できます。

10:49

入力方式の変化

visionOS には新しい入力システムが導入されています。

  • 視線 + 指のピンチを離す = Tap ジェスチャ
  • 視線 + ピンチしたまま手を動かす = Pan ジェスチャ
  • 近距離での直接タッチ = 物理的なタッチ
  • トラックパッド = ペアリング後に利用可能
  • アクセシビリティ = VoiceOver と Switch Control の両方をサポート

このプラットフォームは同時入力を最大 2 つまでサポートします(各手に 1 つ)。例の App では、もともと 4 本指スワイプですべての内容を削除していましたが、visionOS では 2 本指で十分です。

12:17

詳細

空間化された UIKit presentations

UIKit の presentations は visionOS 上で空間的なスタイルを得ます。

Sheet: 表示時に presenting view controller を後方へ押し下げ、暗くします。iPad と異なり、sheet の外側をクリックしても自動では閉じません。

Alert: 上部に App アイコンの 2D 表現を配置します。常に後方へ押し下げられるべき view controller から表示されます。

Popover: visionOS では scene の境界に制限されず、App ウィンドウの外へ広がることができます。ただし permittedArrowDirections の設定には注意が必要です。

14:26

コード例: Popover の矢印方向を修正する

import UIKit

extension EditorViewController {

    @objc func showDocumentPopover(sender: UIBarButtonItem) {
        let controller = DocumentInfoViewController(document: pixelDocument)
        controller.modalPresentationStyle = .popover
        if let presentationController = controller.popoverPresentationController {
            presentationController.barButtonItem = sender
            if traitCollection.userInterfaceIdiom == .reality {
                presentationController.permittedArrowDirections = .any
            } else {
                presentationController.permittedArrowDirections = .right
            }
        }
        present(controller, animated: true, completion: nil)
    }

}

キーポイント:

  • traitCollection.userInterfaceIdiom == .reality で visionOS 上で動作しているかを判定します
  • visionOS では popover が scene 境界に制限されないため、.any を使ってシステムに最適な位置を選ばせます
  • iPad では .right の制限を維持し、popover が画面外へ出るのを避けます

Ornament: コンテンツをシーンの外へ配置する

Ornament は visionOS の新しい概念で、コンテンツを App の scene 周辺に配置できます。システムコンポーネントでも使われています。SwiftUI の TabView は tab bar を leading edge の ornament に置き、Safari は ornament にナビゲーションバーを配置し、Freeform は下部ツールバーに ornament を使います。

Ornament はメインコンテンツの前方に持ち上げられ、奥行き感を加えます。view controller のライフサイクルに紐づき、sheet が表示されても相対位置を保ちます。

17:15

コード例: Ornament を作成する

import UIKit
import SwiftUI

extension EditorViewController {

    func showEditingControlsOrnament() {
        let ornament = UIHostingOrnament(
            sceneAlignment: .bottom,
            contentAlignment: .center
        ) {
            EditingControlsView(model: controlsViewModel)
                .glassBackgroundEffect()
        }

        self.ornaments = [ornament]
        editorView.style = .edgeToEdge
    }

}

キーポイント:

  • UIHostingOrnament は SwiftUI コンテンツをホストするため、import SwiftUI が必要です
  • sceneAlignment: .bottom は ornament を scene 下端に配置します
  • contentAlignment: .center はコンテンツを端の中央にそろえます
  • .glassBackgroundEffect() は ornament にガラス背景を追加します
  • ornaments は UIViewController の新しいプロパティで、ornament の配列を受け取ります
  • ornament は view controller のライフサイクルに紐づき、view controller が削除されると自動で消えます

コード例: セマンティックカラーを使う

private let titleLabelTextField: UITextField = {
    let textField = UITextField()
    textField.textColor = UIColor.label
    return textField
}()

private let authorLabel: UILabel = {
    let label = UILabel()
    label.textColor = UIColor.secondaryLabel
    return label
}()

キーポイント:

  • UIColor.label は主テキスト色で、プラットフォームと外観に自動で適応します
  • UIColor.secondaryLabel は補助テキストに使い、視覚的な階層を下げます
  • セマンティックカラーを使う UILabel は自動で vibrancy エフェクトを得ます
  • RGB 値のハードコードを避けることで、異なるプラットフォームでも一貫した外観を保てます

コード例: テキストフィールドに recessed な外観を追加する

textField.borderStyle = .roundedRect

キーポイント:

  • visionOS では .roundedRect が自動的に recessed な外観で表示されます
  • これはシステムがこのプラットフォーム向けに設計した標準スタイルで、追加設定は不要です

コード例: コンテナ背景スタイルをカスタマイズする

class MyViewController: UIViewController {
    override var preferredContainerBackgroundStyle: UIContainerBackgroundStyle {
        return .glass
    }
}

キーポイント:

  • preferredContainerBackgroundStyle は UIViewController の新しいプロパティです
  • 選べる値は .automatic.glass.hidden です
  • UINavigationController と UISplitViewController は標準でガラス背景を使います

コード例: Hover エフェクトをカスタマイズする

class CollectionViewCell: UICollectionViewCell {
    init(document: PixelArtDocument) {
        super.init(frame: .zero)
        self.hoverStyle = .init(
            effect: .highlight,
            shape: .roundedRect(cornerRadius: 8.0)
        )
    }
}

キーポイント:

  • hoverStyle は UIView の新しいプロパティです
  • effect には .highlight または .lift を選べます
  • shapeUIShape API を使い、角丸矩形などの形状をサポートします
  • nil に設定すると hover エフェクトを削除できます

コード例: プラットフォームに応じてジェスチャを調整する

func fourFingerSwipe() {
    let gesture = UISwipeGestureRecognizer(
        target: self,
        action: #selector(self.deleteAll)
    )
    gesture.direction = .left
    if traitCollection.userInterfaceIdiom == .reality {
        gesture.numberOfTouchesRequired = 2
    } else {
        gesture.numberOfTouchesRequired = 4
    }
    self.view.addGestureRecognizer(gesture)
}

キーポイント:

  • traitCollection.userInterfaceIdiom == .reality は visionOS 判定の推奨方法です
  • visionOS は同時入力を最大 2 つまでサポートするため、2 本指スワイプで十分です
  • iPad では 4 本指スワイプを維持し、ほかのジェスチャとの衝突を避けます

UIKit で RealityKit を使う

RealityView は RealityKit コンテンツをホストするための新しい SwiftUI view です。UIHostingController を使えば、App 全体を書き直さずに UIKit App の中へ RealityView を埋め込めます。

21:56

コード例: UIKit に RealityView を埋め込む

import UIKit
import SwiftUI
import RealityKit

extension EditorViewController {

    func showEntityPreview() {
        let entityView = PixelArtEntityView(model: entityViewModel)
        let controller = UIHostingController(rootView: entityView)
        addChild(controller)
        view.addSubview(controller.view)
        controller.didMove(toParent: self)
        prepareEditorInteractions()
    }

}

キーポイント:

  • UIHostingController は SwiftUI view を UIKit のビュー階層へ埋め込みます
  • PixelArtEntityView は内部で RealityView を使い、RealityKit エンティティをレンダリングします
  • 標準的な child view controller のライフサイクル管理は addChildaddSubviewdidMove(toParent:) です
  • この方法により、既存の UIKit App は書き直しなしで 3D 機能を得られます

重要ポイント

  • 既存の iPad App を visionOS へ移植する

    • 何をするか: 既存の UIKit App(描画ツール、ノート App、リーダーなど)に visionOS ターゲットを追加し、空間内で動作させます
    • なぜ取り組む価値があるか: UIKit App はほぼゼロコストで新しい実行環境を得られ、ユーザーはより広い仮想空間であなたの App を使えます
    • 始め方: Xcode の General タブで visionOS ターゲットを追加し、ビルドエラー(非推奨 API やプラットフォーム非互換 API)を処理し、ハードコードされた色をセマンティックカラーへ置き換えます
  • Ornament でツールバーを再設計する

    • 何をするか: 従来の下部ツールバーやサイドバーを Ornament として切り出し、メインコンテンツ領域を最大化します
    • なぜ取り組む価値があるか: Ornament は 2D ウィンドウの境界を越え、ツールコントロールをメインコンテンツの周囲に浮かせるため、遮らず素早くアクセスできます
    • 始め方: UIHostingOrnament で SwiftUI のツールバー view を包み、適切な sceneAlignmentcontentAlignment を設定し、ornament に .glassBackgroundEffect() を追加します
  • App に 3D プレビュー機能を追加する

    • 何をするか: UIKit App に RealityView を埋め込み、ユーザーが任意の角度から 3D コンテンツを確認できるようにします
    • なぜ取り組む価値があるか: App 全体を書き直す必要はなく、UIHostingController だけで既存 UI に 3D 機能を注入できます
    • 始め方: RealityView を含む SwiftUI view を作成し、UIHostingController で UIKit の view controller に埋め込み、child view controller のライフサイクルで管理します
  • 空間化された Popover インタラクションを設計する

    • 何をするか: visionOS では popover が scene 境界に制限されない特性を活かし、より柔軟なコンテキストメニューを設計します
    • なぜ取り組む価値があるか: popover は App ウィンドウの外へ伸びられるため、より自然な指向性のあるインタラクションを提供できます
    • 始め方: permittedArrowDirections を確認し、visionOS では .any を使ってシステムに最適な位置を自動選択させ、プラットフォーム前提のハードコードを避けます

関連セッション

コメント

GitHub Issues · utterances