From ad1bb92d3e9ce8f42c993164d1048e949f520dca Mon Sep 17 00:00:00 2001 From: Thomas Ricouard Date: Tue, 30 Dec 2025 21:06:04 +0100 Subject: [PATCH] Expand SwiftUI view splitting guidelines in SKILL.md Added detailed recommendations and examples for splitting large SwiftUI view bodies and computed view properties into subviews or dedicated View types. Clarified when to extract subviews and how to structure them for maintainability and reuse. Adjusted section numbering to accommodate the new content. --- swiftui-view-refactor/SKILL.md | 75 +++++++++++++++++++++++++++++++++- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/swiftui-view-refactor/SKILL.md b/swiftui-view-refactor/SKILL.md index 99d6659..c7a6166 100644 --- a/swiftui-view-refactor/SKILL.md +++ b/swiftui-view-refactor/SKILL.md @@ -26,7 +26,78 @@ Apply a consistent structure and dependency pattern to SwiftUI views, with a foc - Inject services and shared models via `@Environment`; keep views small and composable. - Split large views into subviews rather than introducing a view model. -### 3) View model handling (only if already present) +### 3) Split large bodies and view properties +- If `body` grows beyond a screen or has multiple logical sections, split it into smaller subviews. +- Extract large computed view properties (`var header: some View { ... }`) into dedicated `View` types when they carry state or complex branching. +- It's fine to keep related subviews as computed view properties in the same file; extract to a standalone `View` struct only when it structurally makes sense or when reuse is intended. +- Prefer passing small inputs (data, bindings, callbacks) over reusing the entire parent view state. + +Example (extracting a section): + +```swift +var body: some View { + VStack(alignment: .leading, spacing: 16) { + HeaderSection(title: title, isPinned: isPinned) + DetailsSection(details: details) + ActionsSection(onSave: onSave, onCancel: onCancel) + } +} +``` + +Example (long body → shorter body + computed views in the same file): + +```swift +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 } + } + } + } +} +``` + +Example (extracting a complex computed view): + +```swift +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) View model handling (only if already present) - 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 pass them into the view model in the view's `init`. @@ -42,7 +113,7 @@ init(dependency: Dependency) { } ``` -### 4) Observation usage +### 5) Observation usage - For `@Observable` reference types, store them as `@State` in the root view. - Pass observables down explicitly as needed; avoid optional state unless required.