diff --git a/swiftui-performance-audit/SKILL.md b/swiftui-performance-audit/SKILL.md index a852a81..c28123f 100644 --- a/swiftui-performance-audit/SKILL.md +++ b/swiftui-performance-audit/SKILL.md @@ -25,6 +25,7 @@ Collect: Focus on: - View invalidation storms from broad state changes. - Unstable identity in lists (`id` churn, `UUID()` per render). +- Top-level conditional view swapping (`if/else` returning different root branches). - Heavy work in `body` (formatting, sorting, image decoding). - Layout thrash (deep stacks, `GeometryReader`, preference chains). - Large images without downsampling or resizing. @@ -52,6 +53,7 @@ Ask for: Prioritize likely SwiftUI culprits: - View invalidation storms from broad state changes. - Unstable identity in lists (`id` churn, `UUID()` per render). +- Top-level conditional view swapping (`if/else` returning different root branches). - Heavy work in `body` (formatting, sorting, image decoding). - Layout thrash (deep stacks, `GeometryReader`, preference chains). - Large images without downsampling or resizing. @@ -144,6 +146,20 @@ ForEach(items, id: \.self) { item in Avoid `id: \.self` for non-stable values; use a stable ID. +### Top-level conditional view swapping + +```swift +var content: some View { + if isEditing { + editingView + } else { + readOnlyView + } +} +``` + +Prefer one stable base view and localize conditions to sections/modifiers (for example inside `toolbar`, row content, `overlay`, or `disabled`). This reduces root identity churn and helps SwiftUI diffing stay efficient. + ### Image decoding on the main thread ```swift diff --git a/swiftui-view-refactor/SKILL.md b/swiftui-view-refactor/SKILL.md index e22ebf3..3904176 100644 --- a/swiftui-view-refactor/SKILL.md +++ b/swiftui-view-refactor/SKILL.md @@ -97,6 +97,38 @@ private struct HeaderSection: View { } ``` +### 3b) Keep a stable view tree (avoid top-level conditional view swapping) +- Avoid patterns where a computed view (or `body`) returns completely different root branches using `if/else`. +- Prefer a single stable base view, and place conditions inside sections/modifiers (`overlay`, `opacity`, `disabled`, `toolbar`, row content, etc.). +- Root-level branch swapping can cause identity churn, broader invalidation, and extra recomputation in SwiftUI. + +Prefer: + +```swift +var body: some View { + List { + documentsListContent + } + .toolbar { + if canEdit { + editToolbar + } + } +} +``` + +Avoid: + +```swift +var documentsListView: some View { + if canEdit { + editableDocumentsList + } else { + readOnlyDocumentsList + } +} +``` + ### 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. @@ -121,9 +153,10 @@ init(dependency: Dependency) { 1) Reorder the view to match the ordering rules. 2) Favor MV: move lightweight orchestration into the view using `@State`, `@Environment`, `@Query`, `task`, and `onChange`. -3) If a view model exists, replace optional view models with a non-optional `@State` view model initialized in `init` by passing dependencies from the view. -4) Confirm Observation usage: `@State` for root `@Observable` view models, no redundant wrappers. -5) Keep behavior intact: do not change layout or business logic unless requested. +3) Ensure stable view structure: avoid top-level `if`-based branch swapping; move conditions to localized sections/modifiers. +4) If a view model exists, replace optional view models with a non-optional `@State` view model initialized in `init` by passing dependencies from the view. +5) Confirm Observation usage: `@State` for root `@Observable` view models, no redundant wrappers. +6) Keep behavior intact: do not change layout or business logic unless requested. ## Notes