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 では Tab と TabSection 構文、UIKit では UITab と UITabGroup が導入されました。どちらも 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 でカスタマイズを有効にするには、TabView に TabViewCustomization を付加し、@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 でカスタマイズを有効にするには allowsHiding と preferredPlacement を設定します(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 で一貫しています。
重要ポイント
-
新 SDK でビルドして外観の変化を確認する
- なぜやる価値があるか: iPadOS 18 の Tab Bar 外観変更はコード変更なしで有効になる。まずビルドして確認するのが最速
- 始め方: プロジェクトの deployment target を iPadOS 18 に設定し、Xcode 16 でビルド・実行して Tab Bar の自動アップグレードを確認
-
SF Symbols のアウトライン版を使う
- なぜやる価値があるか: システムが Tab Bar と Sidebar のアイコン差異を自動処理。2 セットの画像を用意する必要がない
- 始め方:
TabやUITabでsystemNameパラメータを使い、アウトライン版のシンボル(square.grid.2x2など)を選択
-
検索機能には専用 API を使う
- なぜやる価値があるか:
Tab(role: .search)とUISearchTabは自動的に固定位置とデフォルトアイコンを取得。検索機能の期待される位置に合致 - 始め方: 既存の検索タブをこれらの専用 API に置き換え、手動のアイコン・タイトル設定を削除
- なぜやる価値があるか:
-
タブ数を制御し、Sidebar でより多くのコンテンツを載せる
- なぜやる価値があるか: Tab Bar のタブが多すぎると見つけにくい。Sidebar はより多くの階層を混雑感なく収容できる
- 始め方: 二次的・分類的なコンテンツを
TabSectionやUITabGroupに移し、Tab Bar をスリムに保つ
-
重要なタブのカスタマイズを無効にする
- なぜやる価値があるか: コア機能の入口は常に表示されるべき。ユーザーの誤操作による非表示を防ぐ
- 始め方:
customizationBehavior(.disabled)やpreferredPlacement = .fixedで重要なタブを保護
関連セッション
- What’s new in SwiftUI — TabView の新構文と iPadOS での外観変更
- Enhance your UI animations and transitions — ナビゲーションとプレゼンテーションのアニメーション遷移
- Create custom visual effects with SwiftUI — SwiftUI での視覚効果の作成
- Create custom hover effects in visionOS — visionOS でのインタラクションデザイン
- Add personality to your app through UX writing — コピーでアプリの個性を形作る
コメント
GitHub Issues · utterances