WWDC Quick Look 💓 By SwiftGGTeam
UIKit App をモダン化する

UIKit App をモダン化する

元の動画を見る

ハイライト

iOS 27 では、すべての UIKit App がハードコードされたデバイスタイプや画面方向を捨て、Trait Collection と Size Classes によって任意のウィンドウサイズへ対応する必要があります。iPhone App は iPad と Mac 上で完全に自由リサイズ可能なウィンドウになり、UIScene ライフサイクルへ移行していない App は起動できません。

主要内容

「固定デバイス」から「任意のウィンドウ」へ

以前 UIKit App を書く時、多くの人は次のようにレイアウトを判定していました。

if UIDevice.current.userInterfaceIdiom == .pad {
    // iPad 用レイアウト
} else {
    // iPhone 用レイアウト
}

あるいは画面方向によって UI 構造を決めていました。このロジックは iOS 27 では完全に破綻します。

iPhone App は Mac の iPhone Mirroring 内で、ユーザーが任意の比率へ自由にリサイズできるようになりました。iPad 上で動く iPhone-only App も、固定比率の「大きなスマートフォン」ではなく、他の iPad App と同じように完全にサイズ変更できます。つまり App は 16:9 の Mac ウィンドウで動くこともあれば、縦長の iPhone 画面で動くこともあります。それでも userInterfaceIdiom は常に .phone のままです。

Apple の提案は明快です。デバイスタイプでレイアウト判断をするのをやめ、Size Classes を使います。

UIScene ライフサイクルは必須要件になる

(02:22)

最新 SDK でビルドした App が UIScene ライフサイクルを採用していない場合、その App は起動できません。これは適応型レイアウトの基盤です。Scene がなければ、システムは複数のウィンドウサイズやライフサイクル状態を管理できません。

まだ AppDelegate ベースの構成を使っているなら、できるだけ早く移行する必要があります。Apple は WWDC25 の session “Make your UIKit app more flexible” とドキュメント “Transitioning to the UIKit scene-based life cycle” を参考資料として挙げています。

UIScreen.main との別れ

(03:02)

App が Mac へミラーリングされたり、iPad の外部ディスプレイへ移動されたりすると、Scene に関連付く画面は変わります。UIScreen.main は常に誤った画面情報を返します。

正しい方法は window scene から動的に取得することです。

let screen = window?.windowScene?.screen

さらに良い方法は、screen への依存を完全に取り除き、Trait Collection の代替 API を使うことです。

画面スケールと利用可能領域の正しい取得方法

(03:49)

以前は UIScreen.main.scale でスケールを取得していましたが、今は traitCollection.displayScale を使います。

override func layoutSubviews() {
    super.layoutSubviews()

    let displayScale = traitCollection.displayScale
    // displayScale に基づいて描画ロジックを調整
}

UIKit は layoutSubviewsdraw(_:) などのよく使われるメソッド内での trait 読み取りを自動追跡します。displayScale が変わると、システムはこれらのメソッドを自動的に再呼び出しします。手動の監視は不要です。

自動追跡で覆えない場所では registerForTraitChanges を使います。

let displayScaleTrait: [UITrait] = [UITraitDisplayScale.self]
registerForTraitChanges(displayScaleTrait) {
    (view: GalleryView, previousTraitCollection: UITraitCollection) in
    view.cache.invalidate()
}

(04:36)

利用可能領域も UIScreen.main.bounds ではなく、Scene の有効ジオメトリか View 自身の bounds を見ます。

// SceneDelegate でジオメトリ変更を監視
func windowScene(
    _ windowScene: UIWindowScene,
    didUpdateEffectiveGeometry previousEffectiveGeometry: UIWindowScene.Geometry
) {
    let geometry = windowScene.effectiveGeometry
    let availableSpace = geometry.coordinateSpace.bounds
}

// ViewController で view の bounds を直接見る
override func viewDidLayoutSubviews() {
    super.viewDidLayoutSubviews()
    let availableSpace = view.bounds.size
}

(05:19) (05:35)

Tab Bar と Navigation Bar の新機能

(09:51)

iPhone App は大きなウィンドウで、下部 Tab Bar ではなくサイドバーを表示できるようになりました。

tabBarController.sidebar.preferredPlacement = .sidebar

システムは水平 Size Class に基づいてサイドバーを表示するか判断します。現在の環境が対応しているかは tabBarController.sidebar.isAvailable で確認できます。

常に表示される prominent tab も設定できます。

tabBarController.prominentTabIdentifier = "cart"

(10:53)

Navigation Bar にはスクロール時の最小化制御が追加されました。

navigationItem.barMinimizationBehavior = .always
navigationItem.barMinimizationSafeAreaAdjustment = .never

(11:30)

CoreMotion と CoreLocation の Body プロトコル

(08:12)

UIView は CoreMotion と CoreLocation の Body プロトコルに準拠するようになりました。View を motion manager に直接代入すれば、システムが座標空間の変換を自動処理します。

override func viewDidLoad() {
    super.viewDidLoad()

    motionManager.deviceMotionBody = view
    locationManager.headingBody = view
}

画面方向に基づいて座標の回転を手動計算する必要はもうありません。

Apple Intelligence との統合

(12:56)

iOS 27 の Menu は Ask Siri ボタンを自動表示します。新しい View Annotations API で特定の View に AppEntity を注釈付けすると、Siri はより正確なコンテキストを得られます。

App がドラッグアンドドロップをサポートしている場合、Siri は drag delegate から直接リソースを読み込めます。注意点として、ドラッグは Siri によってジェスチャなしで開始される場合があります。sessionWillBegin でモーダル UI を表示してはいけません。状態を持つ UI 初期化コードは sessionDidMove に置きます。

Xcode 27 の App Modernization Skill

(14:16)

Xcode 27 には App モダン化 skill が組み込まれており、移行作業の多くを自動化できます。

  • UIScreen.main 呼び出しを traitCollection または scene bounds のチェックに置き換える
  • デバイスタイプのチェックを Size Class のチェックに置き換える
  • App ライフサイクルを Scene ライフサイクルへ変換する
  • 複雑な作業では質問し、大きな作業では残作業を示すコメントを追加する

skill をエクスポートして他のツールで使えます。

xcrun agent skills export

(15:05)

詳細

コード例1:window scene から画面情報を取得する

App が Mac へミラーリングされたり、iPad の外部ディスプレイへ移動されたりすると、Scene に関連付く画面は変わります。UIScreen.main は常に誤った画面情報を返します。

正しい方法は window scene から動的に取得することです。

let screen = window?.windowScene?.screen

重要な点UIScreen.main は iOS 27 で非推奨です。複数画面のシナリオに対応できないためです。windowScene.screen は現在の Scene が実際に存在する画面を常に返し、iPhone Mirroring や外部ディスプレイ切り替え時にも自動更新されます。

コード例2:registerForTraitChanges で trait 変化を監視する

UIKit は layoutSubviewsdraw(_:) などのメソッド内での trait 読み取りを自動追跡し、displayScale が変わると自動的に再描画します。しかしキャッシュ無効化やバックグラウンドでのデータ再計算など、自動追跡で覆えない場所では明示的な登録が必要です。

let displayScaleTrait: [UITrait] = [UITraitDisplayScale.self]
registerForTraitChanges(displayScaleTrait) {
    (view: GalleryView, previousTraitCollection: UITraitCollection) in
    view.cache.invalidate()
}

複数 trait の組み合わせも監視できます。

let geometryTraits: [UITrait] = [
    UITraitHorizontalSizeClass.self,
    UITraitVerticalSizeClass.self
]
registerForTraitChanges(geometryTraits) { (vc: MyViewController, _) in
    vc.updateLayoutForCurrentSizeClass()
}

重要な点registerForTraitChanges のクロージャ引数の型は、監視対象オブジェクトの実際の型(GalleryViewMyViewController など)と一致している必要があります。システムは型推論でコールバックを分配します。クロージャは trait 変化時に同期的に呼ばれるため、軽量な状態更新に向きます。非同期処理や複雑なロジックが必要な場合は、クロージャ内で自前の状態機械を起動するのがよいでしょう。

コード例3:Tab Bar のサイドバー対応

iPhone App は大きなウィンドウで自動的にサイドバーナビゲーションへ切り替えられるため、下部領域を節約できます。

tabBarController.sidebar.preferredPlacement = .sidebar

システムは水平 Size Class に基づいてサイドバーを表示するか判断します。現在の環境が対応しているかは tabBarController.sidebar.isAvailable で確認できます。

常に表示される prominent tab も設定でき、サイドバーが折りたたまれても中核機能へアクセスできるようにできます。

tabBarController.prominentTabIdentifier = "cart"

重要な点preferredPlacement = .sidebar は、条件が合う時はサイドバーを優先するようシステムへ伝えます。ウィンドウが compact な水平 Size Class まで縮むと、システムは自動的に下部 Tab Bar へ戻します。手動対応は不要です。prominentTabIdentifier に対応する tab はサイドバーモードで目立つ表示になり、折りたたみ時にはフローティングボタンとして残ります。

適応型レイアウトの主要 API 移行

古い書き方新しい書き方説明
UIScreen.mainwindow?.windowScene?.screenwindow scene から動的に取得
UIScreen.main.scaletraitCollection.displayScaleTrait Collection が自動追跡
UIScreen.main.boundsview.bounds.size または windowScene.effectiveGeometryView または Scene の実際の空間を見る
UIDevice.current.userInterfaceIdiomtraitCollection.horizontalSizeClassSize Class でレイアウト判断する
UIApplication.shared.statusBarOrientationtraitCollection.horizontalSizeClass / verticalSizeClass方向チェックはもう意味を持たない

ゲーム App の特別対応

ゲームは通常、固定方向を必要とします。iOS 27 では UIRequiresFullscreen の動作が調整されました。完全にリサイズを禁止するのではなく、離散的リサイズ(discrete resizing)を有効にします。ウィンドウサイズが変わるたびに、システムは新しいサイズに合う画面設定へ切り替え、ゲームは常に完全な品質で描画します。

テストのすすめ

Xcode 27 の Device Hub と Xcode Previews は「リサイズモードに入る」機能に対応しており、複数シミュレータを切り替えなくても、デバイス端をドラッグして自由にサイズ変更できます。初期検証が終わったら、必ず実機で iPhone Mirroring と iPad 外部ディスプレイのシナリオをテストしてください。

重要な示唆

  • 古いプロジェクトの UIScreen.main をすべて置き換える

    何をするか:プロジェクト内のすべての UIScreen.main 参照を検索し、traitCollection.displayScale または window scene 参照に置き換えます。

    なぜ価値があるか:iOS 27 では UIScreen.main が誤ったデータを返し、画像のぼやけやレイアウトのずれを引き起こします。

    どう始めるか:Xcode の Find Navigator で UIScreen.main を検索し、1つずつ置き換えます。あるいは Xcode 27 の App modernization skill で自動処理します。

  • Tab Bar にサイドバー対応を追加する

    何をするか:viewDidLoadtabBarController?.sidebar.preferredPlacement = .sidebar を設定します。

    なぜ価値があるか:iPhone App が Mac 上で大きなウィンドウにリサイズされた時、下部 Tab Bar は横方向の空間を大きく浪費します。サイドバーならより多くのナビゲーション階層を表示できます。

    どう始めるか:1行のコードで有効化し、sidebar.isAvailable で現在サイドバーが表示されているかを判定して関連 UI を調整します。

  • prominent tab で中核ナビゲーションを守る

    何をするか:最も重要な Tab(カート、ホームなど)に prominentTabIdentifier を設定します。

    なぜ価値があるか:ウィンドウが小さくなるとサイドバーは折りたたまれることがあります。prominent tab は中核機能を常にアクセス可能にします。

    どう始めるか:UITabBarController を作成した後、tabBarController.prominentTabIdentifier = "your-tab-id" を設定します。

  • スクロール時に Navigation Bar を自動的に畳む

    何をするか:navigationItem.barMinimizationBehavior = .always を設定します。

    なぜ価値があるか:コンテンツ領域が広がり、読書体験が良くなります。システムのデフォルトでは特定条件でしか畳まれないため、強制的に有効化すると挙動を統一できます。

    どう始めるか:ViewController の初期化コードでこの属性を設定します。safe area を自前で扱っている場合は、barMinimizationSafeAreaAdjustment = .never も設定します。

  • Xcode skill を自分のワークフローへエクスポートする

    何をするか:xcrun agent skills export を実行し、Xcode の modernization skill を markdown ファイルとしてエクスポートします。

    なぜ価値があるか:Cursor や Claude Code など他のツールでも Apple 公式のモダン化ルールを再利用でき、チームの移行方針を揃えられます。

    どう始めるか:Terminal でコマンドを実行します。エクスポートされたファイルは他の AI コーディングツールへ直接インポートできます。

関連 Session

コメント

GitHub Issues · utterances