swiftui-development について
SwiftUI アプリ開発者向けの実践ガイドで、アーキテクチャ、状態管理、並行処理、レイアウト、アニメーション、テストを扱います。Swift 6 の並行処理安全性、最新 API、保守しやすいビュー設計を重視します。
主な機能
- 01MVVM と Clean Architecture の指針
- 02状態管理と依存性注入の提案
- 03Swift 6 の並行処理とデータフロー
- 04レイアウト、アニメーション、性能の実践
- 05アクセシビリティ、プレビュー、テストの指針
活用シーン
- 01SwiftUI アプリのアーキテクチャ設計
- 02並行処理に安全なビューモデルの実装
- 03リストとアダプティブレイアウトの最適化
- 04画面へのプレビューとアクセシビリティの追加
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ mindrally/ skills --skill swiftui-developmentインストール後、Claude Code で/swiftui-developmentを入力して読み込まれたか確認します。
詳細
SwiftUI 開発
このスキルでは、アーキテクチャ、状態管理、Swift 6 の並行処理、レイアウト、アニメーション、テストなど、明快で高性能、かつ保守しやすい SwiftUI アプリケーションを構築するためのベストプラクティスを解説します。
主な原則
- 正確で最新の、バグがなく完全に機能し、安全かつ高性能なコードを書きます
- 正確性や安全性を損なわない範囲で、読みやすさを優先します
- 要求された機能はすべて完全に実装し、TODO、プレースホルダー、未実装の箇所を残しません
- 厳密な並行処理チェックを有効にした Swift 6.0 以降を対象とし、並行処理に関する警告はエラーとして扱います
- 可能な場合は iOS 17 以降を対象にして、最新の API(
@Observable、NavigationStack、#Preview)を利用します。最小デプロイメントターゲットの制約で古いパターンが必要になる場合は、その旨を明記します
アーキテクチャ
- MVVM(Model-View-ViewModel)またはクリーンアーキテクチャを使用します。複雑な複数画面のナビゲーションフローでは、Coordinator レイヤーを導入します
- ビューとビューモデルに SOLID 原則を適用します:
- 単一責任 — 各ビューまたはビューモデルには、変更する理由が 1 つだけあります
- 開放/閉鎖 — 既存の型を変更するのではなく、コンポジションとプロトコルを使って動作を拡張します
- リスコフの置換 — プロトコル適合型は、呼び出し元に予想外の動作をもたらすことなく置き換え可能である必要があります
- インターフェース分離 — 何でも行う大きなインターフェースではなく、小さく目的を絞ったプロトコルを保ちます
- 依存性逆転 — 具象型ではなくプロトコルによる抽象化に依存し、
init経由で依存関係を注入します
- プロトコル指向プログラミングを実践し、データモデルにはクラスより構造体を優先します
- コードの整理と関心事の分離にはエクステンションを使用します
SwiftUI のビュー構造
- ビューは小さく保ち、単一の責務に集中させます
bodyプロパティは実用上 50 行を上限の目安とします。これを超える場合は、サブビューを小さく再利用可能な構造体またはプライベートなエクステンション関数に切り出します- カスタムコンテナービューや複雑な条件分岐を含むビューのロジックには
@ViewBuilderを使用します - 適切なビューコンポジションのパターンを実装します
状態管理
@State— ローカルな値型のビュー状態(Bool、Int、String、小さな構造体)@Binding— 子ビューとの双方向データバインディング@StateObject— オブジェクトのライフサイクルを作成・所有するビューでのみ使用します@ObservedObject— オブジェクトを所有せず、その変更に反応する子ビューで使用します@EnvironmentObject/@Environment— 広く共有される依存関係やシステム値に限って使用します。アプリ全体ではなく特定のビュー階層に属する依存関係には、init経由の明示的な依存性注入を優先します@Published—ObservableObjectクラスの監視可能なプロパティに使用します(iOS 17 より前、または別の理由でObservableObjectが必要な場合)@Observableマクロ(iOS 17 以降)— 新しいビューモデルではObservableObject/@Publishedよりこちらを優先します。各プロパティに@Publishedを付ける必要がなくなり、ビューが実際に読み取るプロパティが変更された場合にのみビューの更新をトリガーするため、不要な再描画を減らせます
命名規則
- 変数、関数、メソッドには camelCase、型(クラス、構造体、列挙型、プロトコル)には PascalCase を使用します
- 説明的で具体的な名前を使用します —
getDataよりfetchUserDataを優先します - 真偽値を表す変数には
is、has、shouldなどを接頭辞として付けます - 関数名には動詞句を使用します
SwiftUI のベストプラクティス
- システムアイコンには SF Symbols を使用し、自動でダークモードに対応できるよう、アセットカタログのセマンティックカラーを使用します
- Dynamic Type に対応し、適切なキーボード回避処理を実装します
- 非推奨の
NavigationViewよりNavigationStack(iOS 16 以降)を優先します - 利用可能な場合は、型推論による簡潔なドット構文を優先します(例:
.background(.blue)) - 背景が透明な
HStack/VStackの行には.contentShape(Rectangle())を追加し、不透明なコンテンツ部分だけでなく行全体でタップを検出できるようにします
レイアウトとスタイル
- ネイティブの SwiftUI レイアウトコンテナー(VStack、HStack、ZStack、Grid)を使用します。動的なリストや長いリストには
LazyVStack/LazyHStackを使用します GeometryReaderは控えめに使用します。利用可能な領域全体に広がり、レイアウトのパフォーマンスを低下させる可能性があります。代わりに、単一のビューに適用する.background(GeometryReader { ... })や、カスタムレイアウトにLayoutプロトコルを使用してください\.selfではなく、Identifiableに基づく安定した意味のあるidをリスト項目に付与します- さまざまな画面サイズに対応するアダプティブレイアウトを実装します
- スタイルを再利用するには
ViewModifierを使用します。カスタムのButtonStyle、TextFieldStyleなどを作成します
アニメーションとトランジション
- 特に
body内では、広範囲に適用するwithAnimation呼び出しより、特定の状態値にスコープを限定した.animation(_:value:)を優先します withAnimationは、ユーザー操作によって明示的に開始されるアニメーションにのみ使用しますAnyTransitionを使用してカスタムトランジションを実装します。ヒーローアニメーションにはmatchedGeometryEffectを使用します- 高頻度の時間駆動型ビジュアル更新には、
Timerと公開プロパティの組み合わせではなくTimelineViewを使用します
Swift 6 の並行処理とデータフロー
- ビューモデルに
@MainActorを付与します。UI の更新はすべてメインアクター上で行う必要があります - 非同期処理の開始には
.onAppearより.task(id:)を優先します。ビューが消えたとき、または ID が変更されたときにタスクが自動的にキャンセルされ、孤立した処理を防ぎます - 非同期処理には
async/awaitを使用し、エラー処理にはResult(または型付き throws)を使用します - 複数のタスクからアクセスされる共有の可変状態やサービスには、
actor型を優先します - メインアクターにアクセスしない純粋なロジック関数には
nonisolatedを付与し、不要な切り替えを避けます - メインスレッドをブロックしないでください。負荷の高い計算は切り離された
Taskに移します - ローディング、エラー、成功の状態を明示的に処理し、暗黙的または未定義の状態のままにしないでください
メモリ管理と安全性
- 現在のスコープを超えて存続するクロージャでは、原則として
[weak self]を使用します。非同期クロージャの先頭ではguard let self else { return }を使用します [unowned self]は、クロージャの存続期間がselfより短いと確実に証明できる場合にのみ使用します- オプショナルを安全に処理し、強制アンラップはしないでください。早期リターンには
guardを使用します - リモート画像には
AsyncImageを使用し、本番アプリではキャッシュレイヤー、または Nuke/Kingfisher などのライブラリを利用します。また、直ちに.resizable()を適用します
パフォーマンスの最適化
- ビューの
bodyの再計算を最小限に抑えます。SwiftUI が不要な差分計算をスキップするのに役立つ場合は、Equatableに準拠させます Identifiableに準拠した項目と安定した ID を使って、適切なリスト差分計算を実装します- 最適化の前に Instruments でプロファイリングします。
body内で再計算するのではなく、コストの高い計算をキャッシュします
アクセシビリティ
- 適切なアクセシビリティラベル、ヒント、特性を追加し、VoiceOver に対応します
- UI テストで確実に対象を特定できるよう、操作可能な要素にそれぞれ異なる
accessibilityIdentifier文字列を割り当てます - アクセシビリティ機能(Dynamic Type、VoiceOver、Reduce Motion)を有効にしてテストします
テストとプレビュー
- 現実的なモックデータを注入し、
#Preview(Xcode 15 以降)または古いツールチェーンではPreviewProviderを使ったプレビューを必ず用意します - 複数の配色とデバイスサイズでプレビューします
- ユニットテストは Given-When-Then 形式で構成します。外部依存関係にはプロトコルベースのモックを生成し、実際のサービスに接続せずにビューモデルをテストできるようにします
コード品質
- 自己説明的なコードを書きます。コメントは、ひと目では分からないロジックにのみ追加します
- Swift API Design Guidelines に従います
- 長いファイルを整理するには
MARK: - Section Nameを使用し、プライベートヘルパーはprivate extensionに記述します
よく使われるパターン
@Observable ビューモデルを使ったビュー(iOS 17 以降)
@Observable
@MainActor
final class ContentViewModel {
var items: [Item] = []
var isLoading = false
func loadItems() async {
isLoading = true
defer { isLoading = false }
// Load items
}
}
struct ContentView: View {
@State private var viewModel = ContentViewModel()
var body: some View {
List(viewModel.items) { item in
Text(item.name)
}
.task(id: viewModel.items.count) {
await viewModel.loadItems()
}
}
}
ObservableObject ビューモデルを使ったビュー(iOS 17 より前/レガシー)
struct ContentView: View {
@StateObject private var viewModel = ContentViewModel()
var body: some View {
// View implementation
}
}
@MainActor
class ContentViewModel: ObservableObject {
@Published var items: [Item] = []
@Published var isLoading = false
func loadItems() async {
isLoading = true
// Load items
isLoading = false
}
}
再利用可能なビューモディファイア
struct CardModifier: ViewModifier {
func body(content: Content) -> some View {
content
.padding()
.background(Color(.systemBackground))
.cornerRadius(12)
.shadow(radius: 4)
}
}
extension View {
func cardStyle() -> some View {
modifier(CardModifier())
}
}
コメント
Markdown 対応