swiftui-view-refactor
SwiftUIのViewファイルをリファクタリング・レビューするスキルです。小さな専用サブビューへの分割、MV(Model-View)アーキテクチャによるデータフロー、安定したViewツリーの構築、明示的な依存性注入、正しいObservationの使い方を基本方針としています。Viewの整理、長い`body`の分割、インラインアクションや副作用の除去、`some View`を返すcomputed helperの削減、`@Observable`やViewModelの初期化パターンの標準化を行いたいときに使用してください。
description の原文を見る
Refactor and review SwiftUI view files with strong defaults for small dedicated subviews, MV-over-MVVM data flow, stable view trees, explicit dependency injection, and correct Observation usage. Use when cleaning up a SwiftUI view, splitting long bodies, removing inline actions or side effects, reducing computed `some View` helpers, or standardizing `@Observable` and view model initialization patterns.
SKILL.md 本文
SwiftUI View Refactor
Overview
SwiftUI ビューを小規模で明示的で安定した View 型へリファクタリングします。デフォルトは vanilla SwiftUI を使用:ローカル状態はビューに、共有依存関係は environment に、ビジネスロジックはサービス/モデルに、ビューモデルはリクエストまたは既存コードが明確に要求する場合のみ使用します。
Core Guidelines
1) View の順序 (上 → 下)
- 既存ファイルがより強いローカル規約を持つ場合を除き、この順序を適用してください。
- Environment
private/publiclet@State/ その他のストアドプロパティ- computed
var(ビュー以外) initbody- computed view builders / その他のビューヘルパー
- ヘルパー / async 関数
2) デフォルトは MV、MVVM ではない
- ビューは軽量な状態表現とオーケストレーションポイントであるべきで、ビジネスロジックのコンテナではありません。
- ビューモデルに手を出す前に、
@State、@Environment、@Query、.task、.task(id:)、onChangeを優先的に使用してください。 - サービスと共有モデルを
@Environment経由でインジェクトし、ドメインロジックをビュー本体ではなくサービス/モデルに保ちます。 - ローカルビューの状態をミラーリングしたり、environment の依存関係をラップするだけのためにビューモデルを導入しないでください。
- 画面が大きくなっている場合は、新しいビューモデルレイヤーを発明する前に、UI を小規模なサブビューに分割してください。
3) computed some View ヘルパーより専用サブビュー型を強く優先
- 約 1 画面よりも長いか、複数の論理的なセクションを含む
bodyプロパティにフラグを立ててください。 - 非自明なセクション、特に状態、async 作業、分岐を持つか、独自のプレビューがあるセクションについては、専用の
View型の抽出を優先します。 - computed
some Viewヘルパーは稀で小規模に保ってください。private var header: some Viewスタイルのフラグメントで画面全体を構築しないでください。 - 親の全状態を渡すのではなく、小規模で明示的な入力(データ、バインディング、コールバック)を抽出されたサブビューに渡します。
- 抽出されたサブビューが再利用可能または独立して意味のあるものになった場合は、独自のファイルに移動してください。
優先する例:
var body: some View {
List {
HeaderSection(title: title, subtitle: subtitle)
FilterSection(
filterOptions: filterOptions,
selectedFilter: $selectedFilter
)
ResultsSection(items: filteredItems)
FooterSection()
}
}
private struct HeaderSection: View {
let title: String
let subtitle: String
var body: some View {
VStack(alignment: .leading, spacing: 6) {
Text(title).font(.title2)
Text(subtitle).font(.subheadline)
}
}
}
private struct FilterSection: View {
let filterOptions: [FilterOption]
@Binding var selectedFilter: FilterOption
var body: some View {
ScrollView(.horizontal, showsIndicators: false) {
HStack {
ForEach(filterOptions, id: \.self) { option in
FilterChip(option: option, isSelected: option == selectedFilter)
.onTapGesture { selectedFilter = option }
}
}
}
}
}
避ける例:
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)
}
}
3b) body からアクションとサイドエフェクトを抽出
- 非自明なボタンアクションをビュー本体にインラインで保たないでください。
- ビジネスロジックを
.task、.onAppear、.onChange、.refreshableに埋め込まないでください。 - ビューから小規模なプライベートメソッドを呼び出すことを優先し、実際のビジネスロジックをサービス/モデルに移動してください。
- 本体はビューコントローラーのようではなく、UI のように読まれるべきです。
Button("Save", action: save)
.disabled(isSaving)
.task(id: searchText) {
await reload(for: searchText)
}
private func save() {
Task { await saveAsync() }
}
private func reload(for searchText: String) async {
guard !searchText.isEmpty else {
results = []
return
}
await searchService.search(searchText)
}
4) 安定したビュー木を保つ (トップレベルの条件付きビュー交換を避ける)
if/else経由で完全に異なるルートブランチを返すbodyまたは computed ビューを避けてください。- 条件をセクション/修飾子内に置いた単一の安定した基本ビューを優先します (
overlay、opacity、disabled、toolbarなど)。 - ルートレベルのブランチ交換は ID チャーンを引き起こし、より広い無効化とさらなる再計算を招きます。
優先する例:
var body: some View {
List {
documentsListContent
}
.toolbar {
if canEdit {
editToolbar
}
}
}
避ける例:
var documentsListView: some View {
if canEdit {
editableDocumentsList
} else {
readOnlyDocumentsList
}
}
5) ビューモデルの処理 (既に存在するか明示的にリクエストされた場合のみ)
- ビューモデルをデフォルトではなく、レガシーまたは明示的な必要性パターンとして扱ってください。
- リクエストまたは既存コードが明確に要求しない限り、ビューモデルを導入しないでください。
- ビューモデルが存在する場合は、可能な限りそれを非オプショナルにしてください。
init経由でビューに依存関係を渡し、ビューのinitでビューモデルを作成してください。bootstrapIfNeededパターンおよび他の遅延セットアップ回避策を避けてください。
例(Observation ベース):
@State private var viewModel: SomeViewModel
init(dependency: Dependency) {
_viewModel = State(initialValue: SomeViewModel(dependency: dependency))
}
6) Observation の使用
- iOS 17+ の
@Observable参照型については、所有ビューに@Stateとして保存してください。 - observables を明示的に下に渡してください。UI が本当に必要としない限りオプショナル状態を避けてください。
- デプロイメントターゲットが iOS 16 以前を含む場合は、所有者で
@StateObjectを、レガシー observable モデルをインジェクトするときに@ObservedObjectを使用してください。
Workflow
- ビューを順序付けルールに合わせて並び替えます。
bodyからインラインアクションとサイドエフェクトを削除します。ビジネスロジックをサービス/モデルに移動し、ビューには薄いオーケストレーションのみを保ちます。- 専用サブビュー型を抽出して長い本体を短縮します。多くの computed
some Viewヘルパーで画面を再構築しないでください。 - 安定したビュー構造を確保します。トップレベルの
ifベースのブランチ交換を避け、条件をローカライズされたセクション/修飾子に移動してください。 - ビューモデルが存在するか明示的に必要な場合、オプショナルビューモデルを
initで初期化されたノンオプショナル@Stateビューモデルに置き換えてください。 - Observation の使用を確認します。iOS 17+ ではルート
@Observableモデル向けに@Stateを、デプロイメントターゲットが必要とする場合のみレガシーラッパーを使用します。 - 動作を維持します。リクエストされない限り、レイアウトまたはビジネスロジックを変更しないでください。
Notes
- 大規模な条件付きブロックおよび大規模な computed
some Viewプロパティより小規模で明示的なビュー型を優先してください。 - computed ビュー builders を
bodyの下に、非ビュー computed vars をinitの上に保ってください。 - 優れた SwiftUI リファクタリングは、ビューが混合レイアウトと命令型ロジックではなく、データフローとレイアウトとして上から下へ読むようにします。
- MV ファーストガイダンスと根拠については、
references/mv-patterns.mdを参照してください。
Large-view handling
SwiftUI ビューファイルが約 300 行を超える場合、積極的に分割します。複数の computed プロパティ内に複雑さを隠すのではなく、意味のあるセクションを専用の View 型に抽出します。アクションとヘルパー用の private 拡張と // MARK: - コメントを使用します。ただし、拡張を巨大な画面を小規模なビュー型に分割する代わりとして扱わないでください。抽出されたサブビューが再利用されるか、独立して意味のあるものの場合、独自のファイルに移動してください。
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- dimillian
- リポジトリ
- dimillian/skills
- ライセンス
- MIT
- 最終更新
- 不明
Source: https://github.com/dimillian/skills / ライセンス: MIT
関連スキル
agent-browser
AI エージェント向けのブラウザ自動化 CLI です。ウェブサイトとの対話が必要な場合に使用します。ページ遷移、フォーム入力、ボタンクリック、スクリーンショット取得、データ抽出、ウェブアプリのテスト、ブラウザ操作の自動化など、あらゆるブラウザタスクに対応できます。「ウェブサイトを開く」「フォームに記入する」「ボタンをクリックする」「スクリーンショットを取得する」「ページからデータを抽出する」「このウェブアプリをテストする」「サイトにログインする」「ブラウザ操作を自動化する」といった要求や、プログラマティックなウェブ操作が必要なタスクで起動します。
anyskill
AnySkill — あなたのプライベート・スキルクラウド。GitHubを基盤としたリポジトリからエージェントスキルを管理、同期、動的にロードできます。自然言語でクラウドスキルを検索し、オンデマンドでプロンプトを自動ロード、カスタムスキルのアップロードと共有、スキルバンドルの一括インストールが可能です。OpenClaw、Antigravity、Claude Code、Cursorに対応しています。
engram
AIエージェント向けの永続的なメモリシステムです。バグ修正、意思決定、発見、設定変更の後はmem_saveを使用してください。ユーザーが「覚えている」「記憶している」と言及した場合、または以前のセッションと重複する作業を開始する際はmem_searchを使用します。セッション終了前にmem_session_summaryを使用して、コンテキストを保持してください。
skyvern
AI駆動のブラウザ自動化により、任意のウェブサイトを自動化できます。フォーム入力、データ抽出、ファイルダウンロード、ログイン、複数ステップのワークフロー実行など、ユーザーがウェブサイトと連携する必要があるときに使用します。Skyvernは、LLMとコンピュータビジョンを活用して、未知のサイトも自動操作可能です。Python SDK、TypeScript SDK、REST API、MCPサーバー、またはCLIを通じて統合できます。
pinchbench
PinchBenchベンチマークを実行して、OpenClawエージェントの実世界タスクにおけるパフォーマンスを評価できます。モデルの機能テスト、モデル間の比較、ベンチマーク結果のリーダーボード提出、またはOpenClawのセットアップがカレンダー、メール、リサーチ、コーディング、複数ステップのワークフローにどの程度対応しているかを確認する際に使用します。
openui
OpenUIとOpenUI Langを使用してジェネレーティブUIアプリを構築できます。これらはLLM生成インターフェースのためのトークン効率的なオープン標準です。OpenUI、@openuidev、ジェネレーティブUI、LLMからのストリーミングUI、AI向けコンポーネントライブラリ、またはjson-render/A2UIの置き換えについて述べる際に使用します。スキャフォルディング、defineComponent、システムプロンプト、Renderer、およびOpenUI Lang出力のデバッグに対応しています。