swiftui-view-refactor について
SwiftUI ビューをリファクタリングする開発者向けのスキルです。構成、Model-View パターン、Observation の指針を示し、動作を保ちながらビューを小さく保ち、ビジネスロジックをモデルやサービスに置きます。
主な機能
- 01ビューのプロパティ順を統一
- 02軽量な Model-View 構成を優先
- 03複雑な body をサブビューに分割
- 04既存のビューモデルを一貫して扱う
- 05@State による Observation の適用
活用シーン
- 01リファクタリング時の SwiftUI ビュー順序の整理
- 02大きな body の複数セクションへの分割
- 03既存ビューモデルを持つビューのリファクタリング
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ steipete/ agent-scripts --skill swiftui-view-refactorインストール後、Claude Code で/swiftui-view-refactorを入力して読み込まれたか確認します。
同梱ファイルreferences/mv-patterns.md
詳細
SwiftUI ビューのリファクタリング
出典:@Dimillian の Dimillian/Skills(2025-12-31)から転載。
概要
順序、Model-View(MV)パターン、慎重なビューのモデル管理、正しい Observation の使用に重点を置き、SwiftUI ビューに一貫した構造と依存関係のパターンを適用します。
基本ガイドライン
1) ビューの順序(上から下)
- Environment
private/publiclet@State/その他の保存プロパティ- 計算プロパティ
var(ビュー以外) initbody- 計算ビュー ビルダー/その他のビュー ヘルパー
- ヘルパー/非同期関数
2) MV(Model-View)パターンを優先する
- デフォルトでは MV を採用します。ビューは軽量な状態表現とし、ビジネスロジックはモデル/サービスが担います。
- オーケストレーションには
@State、@Environment、@Query、task/onChangeを優先します。 - サービスと共有モデルは
@Environmentで注入し、ビューは小さく、組み合わせ可能な状態に保ちます。 - ビューモデルを導入するのではなく、大きなビューをサブビューに分割します。
3) 大きな body とビューのプロパティを分割する
bodyが画面を超える長さになったり、複数の論理的なセクションを含んだりする場合は、小さなサブビューに分割します。- 大きな計算ビュー プロパティ(
var header: some View { ... })が状態や複雑な分岐を含む場合は、専用のView型に切り出します。 - 関連するサブビューを同じファイル内の計算ビュー プロパティとして保持してもかまいません。独立した
View構造体への切り出しは、構造上適切な場合、または再利用を想定する場合に限ります。 - 親ビューの状態全体を再利用するのではなく、小さな入力(データ、バインディング、コールバック)を渡すことを優先します。
例(セクションの切り出し):
var body: some View {
VStack(alignment: .leading, spacing: 16) {
HeaderSection(title: title, isPinned: isPinned)
DetailsSection(details: details)
ActionsSection(onSave: onSave, onCancel: onCancel)
}
}
例(長い body → 短い body + 同じファイル内の計算ビュー):
var body: some View {
List {
header
filters
results
footer
}
}
private var header: some View {
VStack(alignment: .leading, spacing: 6) {
Text(title).font(.title2)
Text(subtitle).font(.subheadline)
}
}
private var filters: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(filterOptions, id: \.self) { option in
FilterChip(option: option, isSelected: option == selectedFilter)
.onTapGesture { selectedFilter = option }
}
}
}
}
例(複雑な計算ビューの切り出し):
private var header: some View {
HeaderSection(title: title, subtitle: subtitle, status: status)
}
private struct HeaderSection: View {
let title: String
let subtitle: String?
let status: Status
var body: some View {
VStack(alignment: .leading, spacing: 4) {
Text(title).font(.headline)
if let subtitle { Text(subtitle).font(.subheadline) }
StatusBadge(status: status)
}
}
}
4) ビューモデルの扱い(すでに存在する場合のみ)
- リクエストまたは既存のコードで明確に必要とされていない限り、ビューモデルを導入しないでください。
- ビューモデルが存在する場合は、可能な限り非オプショナルにします。
- 依存関係を
init経由でビューに渡し、ビューのinit内でビューモデルに渡します。 bootstrapIfNeededパターンは避けてください。
例(Observation ベース):
@State private var viewModel: SomeViewModel
init(dependency: Dependency) {
_viewModel = State(initialValue: SomeViewModel(dependency: dependency))
}
5) Observation の使用
@Observableの参照型は、ルートビューで@Stateとして保持します。- 必要に応じて Observable を明示的に下位へ渡し、必要でない限りオプショナル状態は避けます。
ワークフロー
- ビューを並べ替え、順序のルールに合わせます。
- MV を優先します。
@State、@Environment、@Query、task、onChangeを使って、軽量なオーケストレーションをビューに移します。 - ビューモデルが存在する場合は、オプショナルなビューモデルを、ビューから依存関係を渡して
initで初期化する非オプショナルな@Stateビューモデルに置き換えます。 - Observation の使用を確認します。ルートの
@Observableビューモデルには@Stateを使い、冗長なラッパーは使用しません。 - 動作を維持します。依頼がない限り、レイアウトやビジネスロジックを変更しないでください。
注記
- 大きな条件ブロックより、小さく明示的なヘルパーを優先します。
- 計算ビュー ビルダーは
bodyの下に、ビュー以外の計算プロパティはinitの上に配置します。 - MV を優先する指針とその理由については、
references/mv-patterns.mdを参照してください。
コメント
Markdown 対応