view-refactor について
view-refactorは、SwiftUIビューの構成と依存関係を整理するスキルです。軽量なModel-Viewパターン、適切なObservationの利用、大きなビュー本体の分割を促し、既存の動作を維持します。
主な機能
- 01ビューのプロパティ順序を統一
- 02大きなビュー本体を分割
- 03Model-Viewパターンを優先
- 04既存のビューモデルを慎重に扱う
- 05Observationと状態を適切に利用
活用シーン
- 01大規模なSwiftUIビューのリファクタリング
- 02ビューのプロパティとヘルパーの整理
- 03状態と依存関係の扱いを確認
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ patrickserrano/ skills --skill view-refactorインストール後、Claude Code で/view-refactorを入力して読み込まれたか確認します。
詳細
SwiftUI ビューのリファクタリング
概要
順序、MV パターン、ViewModel の慎重な扱い、正しい Observation の使用に重点を置き、SwiftUI ビューの構造と依存関係のパターンを一貫させます。
ビューの順序(上から下)
- Environment プロパティ
private/publicのlet定数@State/その他の保存プロパティ- 計算プロパティ
var(ビュー以外) initbody- 計算ビュー生成プロパティ/ビュー用ヘルパー
- ヘルパー/非同期関数
基本ガイドライン
1) MV(Model-View)パターンを優先する
- 基本は MV とします。ビューは軽量な状態表現とし、ビジネスロジックはモデル/サービスが担います
- オーケストレーションには
@State、@Environment、@Query、task、onChangeを優先します - サービスや共有モデルは
@Environmentを介して注入します - ViewModel を導入するのではなく、大きなビューを小さなビューに分割します
2) 大きな body を分割する
body が画面に収まらないほど大きくなる場合や、複数の論理的なセクションがある場合は分割します。
var body: some View {
VStack(alignment: .leading, spacing: 16) {
HeaderSection(title: title, isPinned: isPinned)
DetailsSection(details: details)
ActionsSection(onSave: onSave, onCancel: onCancel)
}
}
または、同じファイル内で計算ビュー プロパティを使用します。
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 }
}
}
}
}
3) ViewModel の扱い(すでに存在する場合のみ)
- リクエストまたは既存のコードから明確に必要だと判断できない限り、ViewModel を導入しないでください
- ViewModel が存在する場合、可能であれば非オプショナルにします
- 依存関係を
init経由でビューに渡し、そこから ViewModel に渡します
@State private var viewModel: SomeViewModel
init(dependency: Dependency) {
_viewModel = State(initialValue: SomeViewModel(dependency: dependency))
}
4) Observation の使用
@Observableの参照型は、ルートビューで@Stateとして保持します- 必要に応じて、オブザーバブルを明示的に子ビューへ渡します
- 必要な場合を除き、オプショナル状態を避けます
リファクタリングのワークフロー
- 順序のルールに従ってビューを並べ替えます
- MV を優先します。
@State、@Environment、task、onChangeを使って、オーケストレーションをビューに移します - ViewModel が存在する場合、オプショナルを、
initで初期化する非オプショナルな@Stateに置き換えます - Observation の使用を確認します。ルートの
@Observableには@Stateを使い、冗長なラッパーは使用しません - 動作を維持します。依頼がない限り、レイアウトやビジネスロジックを変更しません
大きなビューの扱い
SwiftUI のビュー ファイルが約 300 行を超える場合は、次のようにします。
- extension を使って関連するヘルパーをグループ化します
- 非同期関数を専用の
privateextension に移動します // MARK: -コメントを使用します(例:// MARK: - Actions、// MARK: - Subviews)- メインの
structは保存プロパティ、init、bodyに専念させます
struct LargeView: View {
@Environment(Store.self) private var store
@State private var items: [Item] = []
var body: some View {
List {
content
}
.task { await loadItems() }
}
}
// MARK: - Subviews
private extension LargeView {
var content: some View {
ForEach(items) { item in
ItemRow(item: item)
}
}
}
// MARK: - Actions
private extension LargeView {
func loadItems() async {
items = await store.fetchItems()
}
}
チェックリスト
- プロパティが正しい順序になっている(Environment → let → @State → 計算プロパティ → init → body)
- 大きな body がサブビューまたは計算ビュー プロパティに分割されている
- 不要な ViewModel が導入されていない
- 既存の ViewModel が可能な限り非オプショナルになっている
-
@Observable型がルートビューで@Stateとして保持されている - 依存関係が
@Environmentを介して注入されている - 300 行を超える場合、ファイルが MARK コメントで整理されている
- 明示的な依頼がない限り、動作が変更されていない
コメント
Markdown 対応