uikit-expert について
iOS インターフェースを実装、レビュー、改善する開発者向けの UIKit ガイドです。ワークフローと分野別チェックリストで Apple の API 指針を整理し、任意の性能調整より正確性を優先します。
主な機能
- 01分野別のレビューチェックリスト
- 02モダン API への置き換え指針
- 03ライフサイクルと Auto Layout の実践
- 04コレクションビューとナビゲーションのパターン
- 05メモリと並行処理の指針
- 06アクセシビリティと適応型 UI の指針
活用シーン
- 01UIKit コードの正確性レビュー
- 02非推奨 UIKit API のモダン化
- 03コレクションビューと画面遷移の構築
- 04アクセシビリティとメモリ管理の確認
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ ivan-magda/ uikit-expert-skill --skill uikit-expertインストール後、Claude Code で/uikit-expertを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
.tessl-plugin/plugin.jsonreferences/adaptive-appearance.mdanimation-patterns.mdauto-layout.mdcell-configuration.mdconcurrency-main-thread.mdimage-loading.mdkeyboard-scroll.mdlist-performance.mdmemory-management.mdmodern-collection-views.mdmodern-uikit-apis.mdnavigation-patterns.mduikit-swiftui-interop.mdview-controller-lifecycle.md詳細
UIKit エキスパートスキル
概要
ネイティブ API と Apple の公式ドキュメントに記載されたガイダンスを優先します。基本方針:アーキテクチャより事実を重視します(MVVM/VIPER/Coordinator を必須とはしませんが、テスト容易性のためにビジネスロジックは分離します)。まず正しさを確保し、その後にパフォーマンスを改善します。「必ず」や「決して」は正しさに関わる場合に限り、任意の最適化には「提案します」や「検討します」を使います。
ワークフロー
以下の 3 つのモードはいずれも、下記のコアガイドラインに基づきます。各トピックから、該当する参照ファイルに移動できます。次から開始点を選びます。
- レビュー — レビューチェックリストを合否判定の基準として順に確認します。不合格の場合は、対応するコアガイドラインに従って修正します。正しさに関する項目(ライフサイクル、Auto Layout、メモリ、並行処理)をスタイルより優先し、パフォーマンスに関する項目は任意として扱います。
- 改善 — 各問題に対して、非推奨 → モダンな方法の表にある置き換え方法と、対応するコアガイドラインを適用します。パフォーマンス改善(ダウンサンプリング、プリフェッチ、制約の頻繁な変更の修正)は、必須ではなく任意の最適化として提示します。
- 新規実装 — 次の順序で実装し、各手順で該当するコアガイドラインを適用します。
- データフローの設計 — 所有する状態、注入される依存関係、モデルレイヤー
- ライフサイクル — 一度だけのセットアップは
viewDidLoad、ジオメトリはviewIsAppearing - Auto Layout — まとめて有効化し、変更を最小限にする
- コレクションビュー — DiffableDataSource + CompositionalLayout + CellRegistration
- ナビゲーション — 4 つすべての外観スロット、並行遷移のガード
- アニメーション、メモリ(
[weak self]、Task のキャンセル)、並行処理(@MainActor) - 相互運用、画像読み込み、キーボード、Dynamic Type/VoiceOver/ダークモード
- iOS 26+ の機能は
#availableと適切なフォールバックで制御
コアガイドライン
ビューコントローラーのライフサイクル — references/view-controller-lifecycle.md を参照
- 一度だけのセットアップ(サブビュー、制約、デリゲート)には
viewDidLoadを使用します。ジオメトリには使用しません - ジオメトリに依存する処理、トレイトに基づくレイアウト、項目へのスクロールには
viewIsAppearing(iOS 13+ にバックデプロイ済み)を使用します viewDidLayoutSubviewsは複数回呼び出されます。軽量なレイヤーフレームの調整にのみ使用しますviewWillAppearの用途は、トランジションコーディネーターによるアニメーションと、対応関係を保った通知の登録に限ります- すべてのライフサイクルのオーバーライドで必ず
superを呼び出します - 子 VC の包含:
addChild→addSubview→didMove(toParent:)— 必ずこの順序で行います - 開発中は
deinitのログで解放を確認します
Auto Layout — references/auto-layout.md を参照
- プログラムで作成したビューでは、必ず
translatesAutoresizingMaskIntoConstraints = falseを設定します NSLayoutConstraint.activate([])を使用し、個別に.isActive = trueを設定することは避けます- 制約は一度だけ作成し、
isActiveを切り替えるか.constantを変更します。削除して再作成してはいけません - 実行時に優先度を
.required(1000)から、または.requiredに変更してはいけません。999 を使用します - 制約をアニメーションする場合は、定数を更新してから、アニメーションブロック内でスーパービューに対して
layoutIfNeeded()を呼び出します - iOS 26+:
.flushUpdatesオプションを使用すると、制約のアニメーションを簡略化できます - 再利用可能なセル内で、深くネストした UIStackViews は避けます
- 重要な制約には
constraint.identifierを設定し、Unable to simultaneously satisfy constraintsのログを読みやすくします
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false // required on programmatic views
view.addSubview(label)
NSLayoutConstraint.activate([ // one batch = one solve pass
label.centerXAnchor.constraint(equalTo: view.centerXAnchor),
label.centerYAnchor.constraint(equalTo: view.centerYAnchor),
])
コレクションビューとデータソース — references/modern-collection-views.md、references/cell-configuration.md、references/list-performance.md を参照
- 安定した識別子(完全なモデル構造体ではなく、UUID/データベース ID)を使って
UICollectionViewDiffableDataSourceを使用します - コンテンツの更新には
reconfigureItemsを使用し、reloadItemsはセルの種類が変わる場合にのみ使用します - 初期データの読み込みには
applySnapshotUsingReloadDataを使用します(差分計算を回避します) - 単純でないレイアウトには
UICollectionViewCompositionalLayoutを使用します UICollectionView.CellRegistrationを使用します。文字列の識別子や手動の型キャストは使用しません- セルのコンテンツには
UIContentConfiguration、セルの背景にはUIBackgroundConfigurationを使用します - 状態に応じたスタイル設定(選択、ハイライト)には
configurationUpdateHandlerを使用します
// Store the registration once — never create it inside the cell provider (crashes on iOS 15+).
private lazy var cellRegistration = UICollectionView.CellRegistration<PhotoCell, Photo> { cell, _, photo in
cell.configure(with: photo)
}
// Data source is keyed by the stable ID, so the provider receives an id — look up the model, then configure with it.
dataSource = .init(collectionView: collectionView) { [weak self] cv, indexPath, id in
guard let self, let photo = self.photo(for: id) else { return nil }
return cv.dequeueConfiguredReusableCell(using: self.cellRegistration, for: indexPath, item: photo)
}
var snapshot = NSDiffableDataSourceSnapshot<Section, Photo.ID>()
snapshot.appendSections([.main])
snapshot.appendItems(photos.map(\.id))
dataSource.apply(snapshot, animatingDifferences: true)
ナビゲーション — references/navigation-patterns.mdを参照
- 4つすべての
UINavigationBarAppearanceスロット(standard、scrollEdge、compact、compactScrollEdge)を設定します viewWillAppearでnavigationBarに設定するのではなく、viewDidLoadでnavigationItem(VCごと)に外観を設定します- ディープリンクには
setViewControllers(_:animated:)を使用します。順番にpushを呼び出す方法は使用しません - 同時遷移を防ぐため、push/popの前に
transitionCoordinatorを確認します - バーの
prefersLargeTitlesは一度だけ設定し、VCごとにlargeTitleDisplayModeを使用します
アニメーション — references/animation-patterns.mdを参照
UIView.animate— 単純な一度限りのアニメーションに使用し、完了ハンドラーでfinishedを確認しますUIViewPropertyAnimator— ジェスチャー駆動で中断可能です。状態遷移(inactive → active → stopped)に従いますCABasicAnimation— レイヤー専用のプロパティ(cornerRadius、shadow、3D transforms)に使用し、先にモデル値を設定します- iOS 17以降のスプリングAPI:
UIView.animate(springDuration:bounce:)はSwiftUIと動作がそろいます - 制約のアニメーション:レイアウトを更新 → 定数を変更 → スーパービューで
layoutIfNeeded()をアニメーションします
メモリ管理 — references/memory-management.mdを参照
- すべてのエスケープクロージャで、デフォルトとして
[weak self]を使用します - Timer:
[weak self]を使用したブロックベースのAPIを使い、viewWillDisappearで無効化します - CADisplayLink:弱参照プロキシパターンを使用します(ブロックベースのAPIはありません)
- NotificationCenter:クロージャで
[weak self]を使用し、deinitでオブザーバーを削除します - 入れ子のクロージャ:保存する内側のクロージャで
[weak self]を再取得します - デリゲート:
AnyObject制約を付けて、必ずweak var delegate: SomeDelegate?とします deinitで解放を確認します。呼び出されない場合は、強参照循環があります
並行処理 — references/concurrency-main-thread.mdを参照
UIViewControllerは@MainActorです。すべてのサブクラスメソッドは暗黙的にメインアクターになりますTask参照を保持し、deinitではなくviewDidDisappearでキャンセルしますawait後にUIを更新する前にTask.isCancelledを確認しますTask.detachedはアクター分離を継承しません。UIには明示的なMainActor.runが必要です- バックグラウンドから
DispatchQueue.main.syncを呼び出してはいけません。await MainActor.runを使用します
UIKitとSwiftUIの相互運用 — references/uikit-swiftui-interop.mdを参照
- UIHostingController:子VCの完全な包含(
addChild→addSubview→didMove)を行い、保存プロパティとして保持します - Auto Layoutコンテナーには
sizingOptions = .intrinsicContentSize(iOS 16以降)を使用します - UIViewRepresentable:変更可能な状態は
makeUIViewではなくupdateUIViewで設定し、更新ループを防ぎます - コレクションビューのセル内にSwiftUIコンテンツを表示するには、UIHostingConfiguration(iOS 16以降)を使用します
画像の読み込み — references/image-loading.mdを参照
- デコード後のビットマップサイズ = 幅 × 高さ × 4バイト(12MPの写真はRAMを約48MB使用します)
- 表示サイズに合わせてImageIOでダウンサンプリングします。ビットマップ全体を読み込んでからサイズ変更してはいけません
- iOS 15以降:非同期デコードには
byPreparingThumbnail(of:)またはprepareForDisplay()を使用します - セルの再利用:
prepareForReuseでTaskをキャンセルして画像をクリアし、完了時に識別情報を確認します
キーボードとスクロール — references/keyboard-scroll.mdを参照
UIKeyboardLayoutGuide(iOS 15以降)を使用し、コンテンツの下端をview.keyboardLayoutGuide.topAnchorに固定します- iPad:フローティングキーボードでは
followsUndockedKeyboard = trueを設定します - キーボード通知を手動で処理する方法はすべて、レイアウトガイドに置き換えます
アダプティブレイアウトとアクセシビリティ — references/adaptive-appearance.mdを参照
- 非推奨の
traitCollectionDidChangeではなく、registerForTraitChanges(iOS 17以降)を使用します - Dynamic Type:
UIFont.preferredFont(forTextStyle:)とadjustsFontForContentSizeCategory = trueを使用します - ダークモード:セマンティックカラー(
.label、.systemBackground)を使用し、traitの変更時にCGColorを再解決します - VoiceOver:カスタムビューに
accessibilityLabel、accessibilityTraits、accessibilityHintを設定します - 複雑なリスト項目の操作には
UIAccessibilityCustomActionを使用します
クイックリファレンス — 非推奨 API → 最新 API
| 非推奨/レガシー | 最新の代替 API | 導入時期 |
|---|---|---|
traitCollectionDidChange | registerForTraitChanges(_:handler:) | iOS 17 |
| キーボード通知 | UIKeyboardLayoutGuide | iOS 15 |
cell.textLabel/detailTextLabel | UIListContentConfiguration | iOS 14 |
register +文字列による dequeue | UICollectionView.CellRegistration | iOS 14 |
スナップショットでの reloadItems | reconfigureItems | iOS 15 |
barTintColor/isTranslucent | UINavigationBarAppearance(4 つのスロット) | iOS 13 |
(複雑な)UICollectionViewFlowLayout | UICollectionViewCompositionalLayout | iOS 13 |
アニメーション内での手動 layoutIfNeeded() | .flushUpdates オプション | iOS 26 |
| レガシーなアプリのライフサイクル | UIScene + SceneDelegate | iOS 26 で必須 |
ObservableObject +手動での無効化 | @Observable + UIObservationTrackingEnabled | iOS 18 |
レビューチェックリスト
ドメインごとに 1 つのゲートを設けます。確認すべき正確性の項目は、上記の該当するコアガイドラインを参照してください。
- ライフサイクル —
viewDidLoadでジオメトリを扱わない(viewIsAppearingを使用する)。すべてのオーバーライドでsuperを呼び出す。子 VC はaddChild → addSubview → didMoveの順に処理する。deinitで解放を確認する - Auto Layout —
translatesAutoresizingMaskIntoConstraints = falseを設定する。NSLayoutConstraint.activate([])を使用する。頻繁な変更を避ける(isActive/.constantを切り替える)。layoutSubviews内でsetNeedsLayout()を呼び出さない(無限ループになります) - コレクションビュー — 差分更新可能で安定した識別子を使用する。
reloadItemsではなくreconfigureItemsを使用する。重複したスナップショット ID を作らない(BUG_IN_CLIENTクラッシュの原因になります)。文字列による dequeue ではなくCellRegistrationを使用する - ナビゲーション — 4 つすべての外観スロットを設定する。
navigationBarではなくnavigationItemに設定する。同時遷移を防ぐガードを設ける - アニメーション — 用途に合った API を使用する。制約のアニメーションでは flush → update → animate の順に処理する。
CAAnimationでは先にモデル値を設定する - メモリ — エスケープクロージャでは
[weak self]を使用する。viewDidDisappearでTaskをキャンセルする。デリゲートはweakにする。ネストしたクロージャでselfを強参照し直さない - 並行処理 —
awaitの後にTask.isCancelledを確認する。バックグラウンドからDispatchQueue.main.syncを呼び出さない。VC サブクラスに冗長な@MainActorを付けない - 画像 — 表示サイズに合わせてダウンサンプリングする。セルの読み込みは
prepareForReuseでキャンセルし、ID を検証する。NSCacheのサイズはデコード後のバイト数に基づいて設定する - 相互運用 —
UIHostingControllerを保持し、完全な親子関係を構築する。updateUIViewで更新ループを防ぐ - キーボード — 通知ではなく
UIKeyboardLayoutGuideを使用する - 適応性/アクセシビリティ —
registerForTraitChangesを使用する。Dynamic Type に対応する。トレイト変更時に CGColor を再解決する。カスタムビューにaccessibilityLabel/Traitsを設定する - iOS 26 以降 — フォールバックを備えた
#availableガードを設ける。UISceneのライフサイクルを使用する。UIObservationTrackingEnabledの使用を検討する
参考資料
references/view-controller-lifecycle.md— ライフサイクルの順序、viewIsAppearing、子 VC の包含references/auto-layout.md— まとめて有効化、制約の頻繁な変更、優先度、アニメーション、デバッグreferences/modern-collection-views.md— 差分可能なデータソース、コンポジションレイアウト、CellRegistrationreferences/cell-configuration.md— UIContentConfiguration、UIBackgroundConfiguration、configurationUpdateHandlerreferences/list-performance.md— 先読み、セルの再利用、reconfigureItems、スクロールのパフォーマンスreferences/navigation-patterns.md— バーの外観、同時遷移、大きなタイトル、ディープリンクreferences/animation-patterns.md— UIView.animate、UIViewPropertyAnimator、CAAnimation、スプリングreferences/memory-management.md— 保持サイクル、[weak self]、Timer/CADisplayLink/ネストしたクロージャーの落とし穴references/concurrency-main-thread.md— @MainActor、Task のライフサイクル、Swift 6、GCD からの移行references/uikit-swiftui-interop.md— UIHostingController、UIViewRepresentable、サイズ調整、状態の橋渡しreferences/image-loading.md— ダウンサンプリング、デコード済みビットマップの計算、セル再利用時の競合状態references/keyboard-scroll.md— UIKeyboardLayoutGuide、スクロールビューのインセット、iPad のフローティングキーボードreferences/adaptive-appearance.md— トレイトの変更、Dynamic Type、ダークモード、VoiceOver、アクセシビリティreferences/modern-uikit-apis.md— Observation フレームワーク、updateProperties()、.flushUpdates、UIScene、Liquid Glass
コメント
Markdown 対応