🛠️ SwiftuiViewリファクタリング
SwiftUIで作られたアプリの画面を、より小さな部品
📺 まず動画で見る(YouTube)
▶ 【衝撃】最強のAIエージェント「Claude Code」の最新機能・使い方・プログラミングをAIで効率化する超実践術を解説! ↗
※ jpskill.com 編集部が参考用に選んだ動画です。動画の内容と Skill の挙動は厳密には一致しないことがあります。
📜 元の英語説明(参考)
Refactor SwiftUI views into smaller components with stable, explicit data flow.
🇯🇵 日本人クリエイター向け解説
SwiftUIで作られたアプリの画面を、より小さな部品
※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。
下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o swiftui-view-refactor.zip https://jpskill.com/download/3562.zip && unzip -o swiftui-view-refactor.zip && rm swiftui-view-refactor.zip
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/3562.zip -OutFile "$d\swiftui-view-refactor.zip"; Expand-Archive "$d\swiftui-view-refactor.zip" -DestinationPath $d -Force; ri "$d\swiftui-view-refactor.zip"
完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。
💾 手動でダウンロードしたい(コマンドが難しい人向け)
- 1. 下の青いボタンを押して
swiftui-view-refactor.zipをダウンロード - 2. ZIPファイルをダブルクリックで解凍 →
swiftui-view-refactorフォルダができる - 3. そのフォルダを
C:\Users\あなたの名前\.claude\skills\(Win)または~/.claude/skills/(Mac)へ移動 - 4. Claude Code を再起動
⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。
🎯 このSkillでできること
下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。
📦 インストール方法 (3ステップ)
- 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
- 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
- 3. 展開してできたフォルダを、ホームフォルダの
.claude/skills/に置く- · macOS / Linux:
~/.claude/skills/ - · Windows:
%USERPROFILE%\.claude\skills\
- · macOS / Linux:
Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。
詳しい使い方ガイドを見る →- 最終更新
- 2026-05-17
- 取得日時
- 2026-05-17
- 同梱ファイル
- 2
💬 こう話しかけるだけ — サンプルプロンプト
- › Swiftui View Refactor を使って、最小構成のサンプルコードを示して
- › Swiftui View Refactor の主な使い方と注意点を教えて
- › Swiftui View Refactor を既存プロジェクトに組み込む方法を教えて
これをClaude Code に貼るだけで、このSkillが自動発動します。
📖 Skill本文(日本語訳)
※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。
[スキル名] swiftui-view-refactor
SwiftUIビューのリファクタリング
概要
SwiftUIビューを、小さく、明示的で、安定したビュータイプに向けてリファクタリングします。デフォルトでは、バニラのSwiftUIを使用します。つまり、ローカルの状態はビューに、共有される依存関係は環境に、ビジネスロジックはサービス/モデルに配置し、ビューモデルは、リクエストまたは既存のコードが明確に必要とする場合にのみ使用します。
使用する場面
- 大きなSwiftUIビューを整理したり、長い
bodyの実装を分割したりする場合。 - より小さなサブビュー、明示的な依存性注入、またはより良いObservationの使用が必要な場合。
コアガイドライン
1) ビューの順序 (上 → 下)
- 既存のファイルに保持すべきより強力なローカル規約がない限り、この順序を強制します。
- Environment
private/publiclet@State/ その他の保存プロパティ- 算出
var(ビュー以外) initbody- 算出ビュービルダー / その他のビューヘルパー
- ヘルパー / 非同期関数
2) デフォルトはMVであり、MVVMではない
- ビューは軽量な状態表現とオーケストレーションのポイントであるべきで、ビジネスロジックのコンテナであってはなりません。
- ビューモデルに手を出す前に、
@State、@Environment、@Query、.task、.task(id:)、およびonChangeを優先してください。 - サービスと共有モデルは
@Environmentを介して注入し、ドメインロジックはサービス/モデルに保持し、ビューのボディには置かないでください。 - ローカルのビューの状態をミラーリングしたり、環境の依存関係をラップしたりするためだけにビューモデルを導入しないでください。
- 画面が大きくなってきたら、新しいビューモデル層を発明する前に、UIをサブビューに分割してください。
3) 算出 some View ヘルパーよりも専用のサブビュータイプを強く推奨
- おおよそ1画面よりも長い、または複数の論理セクションを含む
bodyプロパティにフラグを立ててください。 - 特に状態、非同期処理、分岐、または独自のプレビューに値する非自明なセクションについては、専用の
Viewタイプを抽出することを優先してください。 - 算出
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や算出ビューを避けてください。- セクション/モディファイア (
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として保存してください。 - オブザーバブルは明示的に渡してください。UIが本当に必要としない限り、オプショナルな状態は避けてください。
- デプロイターゲットにiOS 16以前が含まれる場合は、所有者で
@StateObjectを使用し、レガシーなオブザーバブルモデルを注入する際には@ObservedObjectを使用してください。
ワークフロー
- 並べ替え
📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開
SwiftUI View Refactor
Overview
Refactor SwiftUI views toward small, explicit, stable view types. Default to vanilla SwiftUI: local state in the view, shared dependencies in the environment, business logic in services/models, and view models only when the request or existing code clearly requires one.
When to Use
- When cleaning up a large SwiftUI view or splitting long
bodyimplementations. - When you need smaller subviews, explicit dependency injection, or better Observation usage.
Core Guidelines
1) View ordering (top → bottom)
- Enforce this ordering unless the existing file has a stronger local convention you must preserve.
- Environment
private/publiclet@State/ other stored properties- computed
var(non-view) initbody- computed view builders / other view helpers
- helper / async functions
2) Default to MV, not MVVM
- Views should be lightweight state expressions and orchestration points, not containers for business logic.
- Favor
@State,@Environment,@Query,.task,.task(id:), andonChangebefore reaching for a view model. - Inject services and shared models via
@Environment; keep domain logic in services/models, not in the view body. - Do not introduce a view model just to mirror local view state or wrap environment dependencies.
- If a screen is getting large, split the UI into subviews before inventing a new view model layer.
3) Strongly prefer dedicated subview types over computed some View helpers
- Flag
bodyproperties that are longer than roughly one screen or contain multiple logical sections. - Prefer extracting dedicated
Viewtypes for non-trivial sections, especially when they have state, async work, branching, or deserve their own preview. - Keep computed
some Viewhelpers rare and small. Do not build an entire screen out ofprivate var header: some View-style fragments. - Pass small, explicit inputs (data, bindings, callbacks) into extracted subviews instead of handing down the entire parent state.
- If an extracted subview becomes reusable or independently meaningful, move it to its own file.
Prefer:
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 }
}
}
}
}
}
Avoid:
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) Extract actions and side effects out of body
- Do not keep non-trivial button actions inline in the view body.
- Do not bury business logic inside
.task,.onAppear,.onChange, or.refreshable. - Prefer calling small private methods from the view, and move real business logic into services/models.
- The body should read like UI, not like a view controller.
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) Keep a stable view tree (avoid top-level conditional view swapping)
- Avoid
bodyor computed views that return completely different root branches viaif/else. - Prefer a single stable base view with conditions inside sections/modifiers (
overlay,opacity,disabled,toolbar, etc.). - Root-level branch swapping causes identity churn, broader invalidation, and extra recomputation.
Prefer:
var body: some View {
List {
documentsListContent
}
.toolbar {
if canEdit {
editToolbar
}
}
}
Avoid:
var documentsListView: some View {
if canEdit {
editableDocumentsList
} else {
readOnlyDocumentsList
}
}
5) View model handling (only if already present or explicitly requested)
- Treat view models as a legacy or explicit-need pattern, not the default.
- Do not introduce a view model unless the request or existing code clearly calls for one.
- If a view model exists, make it non-optional when possible.
- Pass dependencies to the view via
init, then create the view model in the view'sinit. - Avoid
bootstrapIfNeededpatterns and other delayed setup workarounds.
Example (Observation-based):
@State private var viewModel: SomeViewModel
init(dependency: Dependency) {
_viewModel = State(initialValue: SomeViewModel(dependency: dependency))
}
6) Observation usage
- For
@Observablereference types on iOS 17+, store them as@Statein the owning view. - Pass observables down explicitly; avoid optional state unless the UI genuinely needs it.
- If the deployment target includes iOS 16 or earlier, use
@StateObjectat the owner and@ObservedObjectwhen injecting legacy observable models.
Workflow
- Reorder the view to match the ordering rules.
- Remove inline actions and side effects from
body; move business logic into services/models and keep only thin orchestration in the view. - Shorten long bodies by extracting dedicated subview types; avoid rebuilding the screen out of many computed
some Viewhelpers. - Ensure stable view structure: avoid top-level
if-based branch swapping; move conditions to localized sections/modifiers. - If a view model exists or is explicitly required, replace optional view models with a non-optional
@Stateview model initialized ininit. - Confirm Observation usage:
@Statefor root@Observablemodels on iOS 17+, legacy wrappers only when the deployment target requires them. - Keep behavior intact: do not change layout or business logic unless requested.
Notes
- Prefer small, explicit view types over large conditional blocks and large computed
some Viewproperties. - Keep computed view builders below
bodyand non-view computed vars aboveinit. - A good SwiftUI refactor should make the view read top-to-bottom as data flow plus layout, not as mixed layout and imperative logic.
- For MV-first guidance and rationale, see
references/mv-patterns.md.
Large-view handling
When a SwiftUI view file exceeds ~300 lines, split it aggressively. Extract meaningful sections into dedicated View types instead of hiding complexity in many computed properties. Use private extensions with // MARK: - comments for actions and helpers, but do not treat extensions as a substitute for breaking a giant screen into smaller view types. If an extracted subview is reused or independently meaningful, move it into its own file.
Limitations
- Use this skill only when the task clearly matches the scope described above.
- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.
- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.
同梱ファイル
※ ZIPに含まれるファイル一覧。`SKILL.md` 本体に加え、参考資料・サンプル・スクリプトが入っている場合があります。
- 📄 SKILL.md (8,209 bytes)
- 📎 references/mv-patterns.md (5,568 bytes)