WWDC Quick Look 💓 By SwiftGGTeam
Elevate your tab and sidebar experience in iPadOS

Elevate your tab and sidebar experience in iPadOS

元の動画を見る

Highlight

iPadOS 18 では Tab Bar が画面上部に移動し、ナビゲーションバーとスペースを共有します。よりコンパクトなデザインでコンテンツ領域が広がり、iPadOS 18 SDK でビルドした既存アプリはコード変更なしで新しい外観を得られます。


主要内容

iPad の Tab Bar は iOS 誕生当初から存在していますが、iPadOS 18 で初の大幅な刷新を迎えました。従来の Tab Bar は画面下部にあり、垂直方向のスペースを消費していました。新デザインでは上部に移動し、ナビゲーションバーと同じセーフエリアを共有します。コンテンツ領域が広がるだけでなく、タブコントロールが戻るボタンやタイトルなど他のナビゲーション要素に近づき、操作しやすくなります。

もう一つの長年の課題は Sidebar の構築方法でした。iPadOS 17 以前では、UISplitViewController と Outline View を組み合わせて Sidebar を実装する必要があり、煩雑で直感的ではありませんでした。iPadOS 18 ではより直接的な解決策を提供します。同じタブ階層の記述を Tab Bar と Sidebar の両方で表示できます。ユーザーが展開ボタンをタップすると、Tab Bar が Sidebar にスムーズに遷移し、より多くの階層コンテンツ(フォルダやプレイリストなど)を表示します。折りたたむとコンパクトな Tab Bar に戻ります。両方の形態は同じ状態データを共有し、シームレスに切り替わります。

パーソナライズもこの新ナビゲーションシステムの重点です。ユーザーは Sidebar からタブを Tab Bar にドラッグして固定でき、不要なタブを非表示にできます。Tab Bar は 3 つのゾーンに分かれます。固定ゾーン(カスタマイズ不可)、カスタマイズゾーン(ドラッグで並べ替え)、ピン留めゾーン(末尾に常に表示、検索など)。これらのカスタマイズ動作はシステムが自動的に永続化し、次回アプリを開いたときに復元されます。

これらの機能をサポートするため、SwiftUI では TabTabSection 構文、UIKit では UITabUITabGroup が導入されました。どちらも 2 つのナビゲーションロジックを別々に維持するのではなく、アプリのナビゲーション階層を統一的に記述するために使います。


詳細

SwiftUI は iOS 18 で TabView 構文を更新しました。従来の tag/selection パターンではなく、Tab 構造体を直接宣言します。各 Tab にはタイトル、アイコン、コンテンツビューがあり、プログラム制御用に selection value をオプションで渡せます。新構文はコンパイル時に型の一貫性をチェックし、ランタイムエラーを回避します(04:27)。

TabView {
    Tab("Watch Now", systemImage: "play") {
        WatchNowView()
    }
    Tab("Library", systemImage: "books.vertical") {
        LibraryView()
    }
    // ...
}

UIKit にも対応する API があります。各トップレベルセクションを記述する UITab インスタンスを作成し、tabBarController.tabs に代入します。UITab インスタンスへの変更は UI に即座に反映されます(04:58)。

tabBarController.tabs = [
    UITab(title: "Watch Now", image: UIImage(systemName: "play"), identifier: "Tabs.watchNow") { _ in
       WatchNowViewController()
    },
    UITab(title: "Library", image: UIImage(systemName: "books.vertical"), identifier: "Tabs.library") { _ in
       LibraryViewController()
    },
    // ...
]

キーポイント:

  • UITab は view controller ではなく軽量な記述構造
  • identifier はタブの一意の ID を追跡し、カスタマイズ機能に関連
  • クロージャの _ パラメータは UITab 自身を表し、後で参照可能

検索機能は各アプリで一般的です。SwiftUI では Tab(role: .search)、UIKit では UISearchTab を提供しています。これらの専用 API を使うと、システムがデフォルトのタイトル、虫眼鏡アイコンを自動設定し、検索タブを Tab Bar の末尾に固定します(05:58)。

// SwiftUI
Tab(role: .search) {
    SearchView()
}

// UIKit
let searchTab = UISearchTab {
    SearchViewController()
}

キーポイント:

  • 検索タブは自動的に固定位置(pinned placement)を取得
  • アイコンとタイトルの手動設定は不要(システムが内蔵)
  • Sidebar にも表示され、通常の並び順に従う

Sidebar を有効にするには、SwiftUI で tabViewStyle.sidebarAdaptable に設定します。TabSection で関連タブをグループ化します。Sidebar では TabSection が展開/折りたたみ可能なグループとして表示され、Tab Bar ではグループ内のタブがフラットに表示されます(06:41)。

TabView {
    Tab("Watch Now", systemImage: "play") {
        // ...
    }
    Tab("Library", systemImage: "books.vertical") {
        // ...
    }
    // ...
    TabSection("Collections") {
        Tab("Cinematic Shots", systemImage: "list.and.film") {
            // ...
        }
        Tab("Forest Life", systemImage: "list.and.film") {
            // ...
        }
        // ...
    }
    TabSection("Animations") {
        // ...
    }
    Tab(role: .search) {
        // ...
    }
}
.tabViewStyle(.sidebarAdaptable)

キーポイント:

  • TabSection は Sidebar でグループヘッダーとして表示
  • タブは宣言順に並び、Section は個別タブの後に配置
  • 検索タブの位置は Section の影響を受けず、システムが固定位置を決定

UIKit で Sidebar を有効にするには tabBarController.mode.tabSidebar に設定します。UITabGroup でグループ化し、children プロパティを動的に更新できます(07:16)。

let collectionsGroup = UITabGroup(
    title: "Collections",
    image: UIImage(systemName: "folder"),
    identifier: "Tabs.CollectionsGroup"
    children: self.collectionsTabs()) { _ in
        // ...
}

tabBarController.mode = .tabSidebar
tabBarController.tabs = [
    UITab(title: "Watch Now", ...) { _ in
        // ...
    },
    UITab(title: "Library", ...) { _ in
        // ...
    },
    // ...
    collectionsGroup,
    UITabGroup(title: "Animations", ...) { _ in
        // ...
    },
    UISearchTab { _ in
        // ...
    },
]

キーポイント:

  • UITabGroup は可変オブジェクトで、children の変更が UI に即座に反映
  • identifier はグループ ID を追跡し、カスタマイズ機能をサポート
  • mode プロパティで表示モードを決定し、動的に切り替え可能

Sidebar はグループにアクションボタンもサポートします。ポッドキャストアプリの「新規ステーション作成」などです。SwiftUI では sectionActions、UIKit では sidebarActions を使います(07:45)。

TabSection(...) {
    // ...
}
.sectionActions {
    Button("New Station", ...) {
        // action
    }
}

// UIKit
let tabGroup = UITabGroup(...)
tabGroup.sidebarActions = [
    UIAction(title: "New Station", ...) { _ in
        // action
    },
]

キーポイント:

  • アクションボタンはグループヘッダーの右側に表示
  • アクションをクリックしても現在選択中のタブは変わらない
  • グループごとに異なるアクションを設定可能

タブはドロップ先にもなれます。写真アプリでは、ユーザーが写真をコレクションタブにドラッグして直接追加できます。SwiftUI では dropDestination modifier、UIKit では UITabBarControllerDelegate の 2 メソッドを実装します(08:12)。

// SwiftUI
Tab(collection.name, image: collection.image) {
    CollectionDetailView(collection)
}
.dropDestination(for: Photo.self) { photos in
    // Add 'photos' to the specified collection
}

// UIKit
func tabBarController(
    _ tabBarController: UITabBarController,
    tab: UITab, operationForAcceptingItemsFrom dropSession: any UIDropSession
) -> UIDropOperation {
    session.canLoadObjects(ofClass: Photo.self) ? .copy : .cancel
}

func tabBarController(
    _ tabBarController: UITabBarController,
    tab: UITab, acceptItemsFrom dropSession: any UIDropSession) {
    session.loadObjects(ofClass: Photo.self) { photos in
        // Add 'photos' to the specified collection
    }
}

キーポイント:

  • 最初のメソッドでドロップコンテンツを受け入れられるか判定
  • 2 番目のメソッドで実際のデータオブジェクトを受け取る
  • Tab Bar と Sidebar の両方でドロップ操作が有効

ユーザーカスタマイズは今回のアップデートの大きなハイライトです。SwiftUI でカスタマイズを有効にするには、TabViewTabViewCustomization を付加し、@AppStorage で状態を永続化します(10:45)。

@AppStorage("MyTabViewCustomization")
private var customization: TabViewCustomization

TabView {
    Tab("Watch Now", systemImage: "play", value: .watchNow) {
        // ...
    }
    .customizationID("Tab.watchNow")
    // ...
    TabSection("Collections") {
        ForEach(MyCollectionsTab.allCases) { tab in
            Tab(...) {
                // ...
            }
            .customizationID(tab.customizationID)
        }
    }
    .customizationID("Tab.collections")
    // ...
}
.tabViewCustomization($customization)

キーポイント:

  • customizationID はカスタマイズに参加するタブの一意の ID
  • @AppStorage でカスタマイズ状態がアプリ再起動後も復元
  • value パラメータでプログラム的にタブを選択可能

コア機能の入口など、カスタマイズすべきでないタブもあります。customizationBehavior でカスタマイズを無効にできます(11:40)。

Tab("Watch Now", systemImage: "play", value: .watchNow) {
    // ...
}
.customizationBehavior(.disabled, for: .sidebar, .tabBar)

Tab("Optional Tab", ...) {
    // ...
}
.customizationID("Tab.example.optional")
.defaultVisibility(.hidden, for: .tabBar)

キーポイント:

  • .disabled は sidebar/tabBar でタブをカスタマイズ不可にする
  • .defaultVisibility(.hidden) でタブをデフォルト非表示にし、ユーザーが追加可能
  • カスタマイズに参加しないタブは customizationID 不要

UIKit でカスタマイズを有効にするには allowsHidingpreferredPlacement を設定します(12:38)。

let myTab = UITab(...)
myTab.allowsHiding = true
print(myTab.isHidden)

// .default, .optional, .movable, .pinned, .fixed, .sidebarOnly
myTab.preferredPlacement = .fixed

let myTabGroup = UITabGroup(...)
myTabGroup.allowsReordering = true
myTabGroup.displayOrderIdentifiers = [...]

キーポイント:

  • allowsHiding でユーザーがタブを非表示にできるか制御
  • preferredPlacement でタブのデフォルト位置と動作を決定
  • displayOrderIdentifiers でユーザーがカスタマイズした順序を記録

UIKit は delegate メソッドでカスタマイズの変更を通知します(12:39)。

func tabBarController(_ tabBarController: UITabBarController, visibilityDidChangeFor tabs: [UITab]) {
    // Read 'tab.isHidden' for the updated visibility.
}

func tabBarController(_ tabBarController: UITabBarController, displayOrderDidChangeFor group: UITabGroup) {
    // Read 'group.displayOrderIdentifiers' for the updated order.
}

キーポイント:

  • 最初のメソッドはタブの可視性が変わったときに呼ばれる
  • 2 番目のメソッドはグループの表示順序が変わったときに呼ばれる
  • これらのメソッドで他の UI やデータモデルと同期可能

アイコンスタイルについて: Tab Bar は塗りつぶしスタイルの glyph を好み、Sidebar はアウトラインスタイルを好みます。SF Symbols を使う場合はアウトライン版のみ提供すれば、システムが Tab Bar で自動的に filled 版に切り替えます(05:25)。この動作は iPad、iPhone、visionOS で一貫しています。


重要ポイント

  1. 新 SDK でビルドして外観の変化を確認する

    • なぜやる価値があるか: iPadOS 18 の Tab Bar 外観変更はコード変更なしで有効になる。まずビルドして確認するのが最速
    • 始め方: プロジェクトの deployment target を iPadOS 18 に設定し、Xcode 16 でビルド・実行して Tab Bar の自動アップグレードを確認
  2. SF Symbols のアウトライン版を使う

    • なぜやる価値があるか: システムが Tab Bar と Sidebar のアイコン差異を自動処理。2 セットの画像を用意する必要がない
    • 始め方: TabUITabsystemName パラメータを使い、アウトライン版のシンボル(square.grid.2x2 など)を選択
  3. 検索機能には専用 API を使う

    • なぜやる価値があるか: Tab(role: .search)UISearchTab は自動的に固定位置とデフォルトアイコンを取得。検索機能の期待される位置に合致
    • 始め方: 既存の検索タブをこれらの専用 API に置き換え、手動のアイコン・タイトル設定を削除
  4. タブ数を制御し、Sidebar でより多くのコンテンツを載せる

    • なぜやる価値があるか: Tab Bar のタブが多すぎると見つけにくい。Sidebar はより多くの階層を混雑感なく収容できる
    • 始め方: 二次的・分類的なコンテンツを TabSectionUITabGroup に移し、Tab Bar をスリムに保つ
  5. 重要なタブのカスタマイズを無効にする

    • なぜやる価値があるか: コア機能の入口は常に表示されるべき。ユーザーの誤操作による非表示を防ぐ
    • 始め方: customizationBehavior(.disabled)preferredPlacement = .fixed で重要なタブを保護

関連セッション

コメント

GitHub Issues · utterances