Apple プラットフォーム(iOS / iPadOS / macOS)向け UI ガイドライン。Human Interface Guidelines (HIG)、Liquid Glass、SwiftUI レイアウト、Dynamic Type、SF Symbols、VoiceOver、サイズクラス、ナビゲーション構造、ツールバー、シート、アイコンを確認・適用したいときに使用。Use when: designing or implementing UI for iOS, iPadOS, or macOS apps with SwiftUI; applying Apple HIG; adopting Liquid Glass; reviewing Apple platform UI code.
このスキルは Apple プラットフォーム(iOS 26 / iPadOS 26 / macOS 26 Tahoe 以降)向け UI の設計・実装規約を定義します。 Human Interface Guidelines (HIG) に準拠し、Liquid Glass マテリアルを正しく扱うためのルールをまとめています。
参考:
各セクションの指示には以下のタグが付くことがあります。対象プラットフォームに応じて取捨選択してください。
Color.accentColor や ShapeStyle のセマンティックカラーを使用する。accessibilityLabel / accessibilityHint を設定する。.body・.headline 等)または Font.custom(_:size:relativeTo:) を使用し、固定サイズを避ける。NavigationStack / NavigationSplitView / TabView / ツールバー / シート / ポップオーバーは Liquid Glass を自動適用する。background / visualEffect を設定しない。
.toolbarBackground(.hidden, for: .windowToolbar) が許容される(HIG: "Consider temporarily hiding toolbars for a distraction-free experience")。glassEffect の過剰使用を禁止する。 glassEffect(_:in:) はカスタムコントロールなど最も重要な機能要素に限定する。NavigationStack を基本とする。NavigationStack(path: $path) {
ContentView()
.navigationDestination(for: Item.self) { item in
DetailView(item: item)
}
}
NavigationSplitView でサイドバーレイアウトを実現する。NavigationSplitView {
SidebarView()
} detail: {
DetailView()
}
inspector(isPresented:content:) を使用する。backgroundExtensionEffect() を適用してエッジトゥエッジ表現を実現する。Image("hero")
.resizable()
.scaledToFill()
.backgroundExtensionEffect()
| 項目 | iOS | iPadOS |
|---|---|---|
| 表示位置 | 画面下部(フローティング) | 画面上部 |
| サイドバー変換 | 非対応 | .sidebarAdaptable で変換可 |
| カスタマイズ | — | TabViewCustomization で項目追加・削除可 |
TabView {
Tab("ホーム", systemImage: "house.fill") { HomeView() }
Tab("ライブラリ", systemImage: "books.vertical.fill") { LibraryView() }
Tab(role: .search) { SearchView() }
}
.tabViewStyle(.sidebarAdaptable) // 【iPadOS】
.tabBarMinimizeBehavior(.onScrollDown)TabViewCustomization でユーザーがタブ項目を追加・削除できるようにする。.tabViewStyle(.sidebarAdaptable) を採用し、サイドバーへ自動変換させる。ToolbarSpacer で区切る。.toolbar {
ToolbarItemGroup(placement: .bottomBar) { // 【iOS】
Button("編集", systemImage: "pencil") { }
Button("削除", systemImage: "trash") { }
}
ToolbarSpacer(.fixed)
ToolbarItemGroup(placement: .bottomBar) {
Button("共有", systemImage: "square.and.arrow.up") { }
}
}
accessibilityLabel を設定。.hidden() ではなく ToolbarContent/hidden(_:) を使用。scrollEdgeEffectStyle を設定。.buttonStyle(.glass) / .buttonStyle(.glassProminent) を使用。Button("追加") { }.buttonStyle(.glass)
Button("確認") { }.buttonStyle(.glassProminent)
ConcentricRectangle または rect(corners:isUniform:) で周囲要素と同心円的に揃える。GlassEffectContainer + glassEffectID(_:in:) でまとめる。GlassEffectContainer {
ForEach(items) { item in
ItemView(item: item)
.glassEffect(.regular, in: .capsule)
.glassEffectID(item.id, in: namespace)
}
}
presentationDetents で適切なサイズ制御。.sheet(isPresented: $showSheet) {
SheetContentView()
.presentationDetents([.medium, .large])
.presentationDragIndicator(.visible)
}
visualEffectView 等)は削除し、システムの Liquid Glass 背景に委ねる。confirmationDialog を使用し、presenting パラメーターで起点データを指定。.confirmationDialog(
"操作を選択",
isPresented: $showDialog,
titleVisibility: .visible,
presenting: selectedItem
) { item in
Button("削除", role: .destructive) { delete(item) }
Button("共有") { share(item) }
}
.formStyle(.grouped) を使用。Form {
Section("設定") {
Toggle("通知", isOn: $notificationsEnabled)
Slider(value: $volume, in: 0...1)
}
}
.formStyle(.grouped)
Section ヘッダーは Title Case(ALL CAPS は使わない)。.swipeActions(edge: .trailing, allowsFullSwipe: true) {
Button("削除", role: .destructive) { delete(item) }
}
.contextMenu {
Button("削除", role: .destructive) { delete(item) }
Button("共有") { share(item) }
}
Tab(role: .search) で定義。システムがトレーリング端に自動配置する。.largeTitle / .title / .headline / .body / .callout / .subheadline / .footnote / .caption)を使用。Font.custom(_:size:relativeTo:) で Dynamic Type に追従させる。tracking / lineSpacing の独自設定は最小限。Text("タイトル")
.font(.title2)
.fontWeight(.semibold)
Text("説明文")
.font(.body)
.foregroundStyle(.secondary)
8, 16, 24, 32 の倍数を使用。enum Spacing {
static let xs: CGFloat = 8
static let sm: CGFloat = 16
static let md: CGFloat = 24
static let lg: CGFloat = 32
}
ignoresSafeArea(.container, edges: .top)。backgroundExtensionEffect()。contentMargins / safeAreaPadding / scrollEdgeEffectStyle を適切に設定。.animation(.spring(duration: 0.3), value:)。.linear は特別な理由なく使わない。matchedGeometryEffect。glassEffectID(_:in:) + withAnimation。@Environment(\.accessibilityReduceMotion) var reduceMotion
.animation(reduceMotion ? .none : .spring(duration: 0.3), value: isExpanded)
.primary.secondary.tertiary.quaternaryColor.accentColor。ハードコード RGB を避ける。.shadow(radius:) や .foregroundStyle(.primary) で視認性確保。@Environment(\.horizontalSizeClass) でレイアウト幅クラスを判定:
@Environment(\.horizontalSizeClass) var horizontalSizeClass
var body: some View {
if horizontalSizeClass == .compact {
VStack { ... }
} else {
HStack { ... }
}
}
@Environment(\.verticalSizeClass):
regularcompact.sensoryFeedback(_:trigger:) を使用。UIImpactFeedbackGenerator は SwiftUI 非対応時のみ。Button("削除") { delete() }
.sensoryFeedback(.warning, trigger: isDeleted)
Toggle("通知", isOn: $enabled)
.sensoryFeedback(.selection, trigger: enabled)
.success.warning.error.selection.impact(weight: .light)ContentUnavailableView を使用する。独自の空画面は作らない。if items.isEmpty {
ContentUnavailableView(
"アイテムがありません",
systemImage: "tray",
description: Text("新しいアイテムを追加してください。")
)
}
ContentUnavailableView.search(text: searchText)
.redacted(reason: .placeholder) でスケルトン表示。ProgressView() の全画面表示は避ける。ItemRowView(item: placeholderItem)
.redacted(reason: isLoading ? .placeholder : [])
ViewThatFits で代替レイアウトを提供。ViewThatFits {
HStack { LabelView(); ValueView() }
VStack { LabelView(); ValueView() }
}
frame(width:) を避け、.frame(maxWidth: .infinity) / .fixedSize() を優先。containerRelativeFrame で機種差(幅 375〜440 pt)を吸収。GeometryReader の過剰使用を避ける。@FocusState でフォーカスを明示的に管理。@FocusState private var isFieldFocused: Bool
TextField("名前", text: $name)
.focused($isFieldFocused)
KeyboardShortcut を割り当てる。Button("新規作成") { createItem() }
.keyboardShortcut("n", modifiers: .command)
Button("保存") { save() }
.keyboardShortcut("s", modifiers: .command)
NavigationSplitView でリサイズ時のフルードトランジションを自動取得。safeAreaInsets / レイアウトガイドを正しく設定し、ウィンドウコントロールとタイトルバーの重なりを防ぐ。NavigationSplitView の列幅は固定しない。Icon Composer(Xcode 26 内蔵)でレイヤー構造のアイコンを作成する。
| プラットフォーム | バリアント |
|---|---|
| 【iOS / iPadOS】 | Default (Light) / Dark / Clear / Tinted |
| 【macOS】 | Default (Light) / Dark / Clear (Light) / Clear (Dark) / Tinted (Light) / Tinted (Dark) |
HIG は「最大・最小レイアウトを先にテストせよ」と明言。以下機種で必ず確認:
| テスト対象 | 幅 | 高さ | 確認ポイント |
|---|---|---|---|
| iPhone SE(4.7-inch) | 375 | 667 | 最小クラス。コンテンツ欠損なし |
| iPhone 17 Pro Max | 440 | 956 | 最大クラス。横向き時 Regular 幅 |
確認事項:
verticalSizeClass ベースの切替が正しいskills/ui-accessibility/SKILL.mdskills/ui-review-checklist/SKILL.mdskills/swift-coding-standards/SKILL.md