diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 5d1dbefc0..fd42c1ef4 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -27,6 +27,17 @@ jobs:
exit 1
fi
+ skill-references:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ - name: Setup Node.js
+ uses: actions/setup-node@v6
+ with:
+ node-version: '24'
+ - name: Validate skill references
+ run: node scripts/check-skill-references.mjs
+
build:
runs-on: ubuntu-latest
diff --git a/scripts/check-skill-references.mjs b/scripts/check-skill-references.mjs
new file mode 100644
index 000000000..4102fde5d
--- /dev/null
+++ b/scripts/check-skill-references.mjs
@@ -0,0 +1,166 @@
+#!/usr/bin/env node
+
+/**
+ * Validates that relative .md file references in bundled skills point to
+ * files that actually exist on disk.
+ *
+ * Focused on catching broken cross-file references within skills:
+ * - Markdown links to .md files: [text](path/to/file.md)
+ * - Backtick-quoted .md paths that use relative navigation: `../foo/bar.md`
+ * or skill subdirectory paths: `references/foo.md`, `workflows/bar.md`
+ *
+ * Deliberately ignores:
+ * - URLs (http://, https://)
+ * - Paths starting with ~ (home-dir references, not repo-relative)
+ * - Glob patterns containing * or {}
+ * - Template placeholders containing {{ or {word}
+ * - Bare extensions like `.md`, `.ts`
+ * - Example/placeholder paths (path/to/...)
+ * - Paths that reference files outside the skills tree via ../ beyond the
+ * skills root (those are cross-concern refs, not validatable here)
+ *
+ * Exit 0 if all references resolve. Exit 1 if any are broken.
+ */
+
+import { readFileSync, readdirSync, existsSync } from "node:fs";
+import { join, resolve, dirname, extname } from "node:path";
+
+const SKILLS_DIR = resolve("src/resources/skills");
+
+/** Recursively collect all .md files under a directory. */
+function collectMdFiles(dir) {
+ const results = [];
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
+ const full = join(dir, entry.name);
+ if (entry.isDirectory()) {
+ results.push(...collectMdFiles(full));
+ } else if (entry.isFile() && extname(entry.name) === ".md") {
+ results.push(full);
+ }
+ }
+ return results;
+}
+
+/** Return true if this reference should be validated. */
+function shouldValidate(ref) {
+ // Must end with .md (we only validate markdown cross-references)
+ if (!ref.endsWith(".md")) return false;
+ // Skip URLs
+ if (/^https?:\/\//.test(ref)) return false;
+ // Skip home-dir paths
+ if (ref.startsWith("~")) return false;
+ // Skip glob patterns
+ if (/[*{}]/.test(ref)) return false;
+ // Skip template placeholders like {{foo}} or {foo}
+ if (/\{[^}]+\}/.test(ref)) return false;
+ // Skip bare extensions like ".md"
+ if (/^\.\w+$/.test(ref)) return false;
+ // Skip obvious example paths
+ if (/^path\/to\//.test(ref)) return false;
+ // Skip absolute paths
+ if (ref.startsWith("/")) return false;
+ // Only validate paths that look like structural skill references:
+ // relative navigation (../ or ./) or skill subdirectories (references/, workflows/)
+ if (
+ !ref.startsWith("./") &&
+ !ref.startsWith("../") &&
+ !ref.startsWith("references/") &&
+ !ref.startsWith("workflows/") &&
+ !ref.startsWith("scripts/") &&
+ !ref.startsWith("templates/")
+ ) {
+ return false;
+ }
+ return true;
+}
+
+/** Strip trailing anchor: foo.md#section -> foo.md */
+function stripAnchor(ref) {
+ const idx = ref.indexOf("#");
+ return idx >= 0 ? ref.slice(0, idx) : ref;
+}
+
+/**
+ * Extract validatable .md references from markdown content.
+ * Returns array of { ref, line }.
+ */
+function extractReferences(content) {
+ const refs = [];
+ const lines = content.split("\n");
+
+ let inCodeBlock = false;
+
+ for (let i = 0; i < lines.length; i++) {
+ const line = lines[i];
+ const lineNum = i + 1;
+
+ // Track fenced code blocks (``` or ~~~)
+ if (/^(\s*)(`{3,}|~{3,})/.test(line)) {
+ inCodeBlock = !inCodeBlock;
+ continue;
+ }
+ if (inCodeBlock) continue;
+
+ // Pattern 1: Markdown links [text](path.md) or [text](path.md#anchor)
+ const mdLinkRe = /\[(?:[^\]]*)\]\(([^)]+)\)/g;
+ let match;
+ while ((match = mdLinkRe.exec(line)) !== null) {
+ const raw = stripAnchor(match[1].trim());
+ if (shouldValidate(raw)) {
+ refs.push({ ref: raw, line: lineNum });
+ }
+ }
+
+ // Pattern 2: Backtick-quoted paths to .md files
+ const backtickRe = /`([^`]+\.md(?:#[^`]*)?)`/g;
+ while ((match = backtickRe.exec(line)) !== null) {
+ const raw = stripAnchor(match[1].trim());
+ if (shouldValidate(raw)) {
+ refs.push({ ref: raw, line: lineNum });
+ }
+ }
+ }
+
+ return refs;
+}
+
+// --- Main ---
+
+if (!existsSync(SKILLS_DIR)) {
+ console.error(`Skills directory not found: ${SKILLS_DIR}`);
+ process.exit(1);
+}
+
+const mdFiles = collectMdFiles(SKILLS_DIR);
+let brokenCount = 0;
+let checkedCount = 0;
+
+for (const file of mdFiles) {
+ const content = readFileSync(file, "utf-8");
+ const refs = extractReferences(content);
+ const fileDir = dirname(file);
+ const displayPath = file.replace(resolve(".") + "/", "");
+
+ for (const { ref, line } of refs) {
+ checkedCount++;
+ const resolved = resolve(fileDir, ref);
+ if (!existsSync(resolved)) {
+ console.error(
+ `ERROR: ${displayPath}:${line} references "${ref}" but file does not exist`
+ );
+ brokenCount++;
+ }
+ }
+}
+
+if (brokenCount > 0) {
+ console.error(
+ `\n${brokenCount} broken reference(s) found across ${mdFiles.length} skill files.`
+ );
+ process.exit(1);
+} else {
+ console.log(
+ `All references valid. Checked ${checkedCount} reference(s) across ${mdFiles.length} skill file(s).`
+ );
+ process.exit(0);
+}
diff --git a/src/resources/skills/core-web-vitals/SKILL.md b/src/resources/skills/core-web-vitals/SKILL.md
index 0979f0c69..1365070c1 100644
--- a/src/resources/skills/core-web-vitals/SKILL.md
+++ b/src/resources/skills/core-web-vitals/SKILL.md
@@ -438,4 +438,4 @@ startTransition(() => setExpensiveState(newValue));
- [web.dev LCP](https://web.dev/articles/lcp)
- [web.dev INP](https://web.dev/articles/inp)
- [web.dev CLS](https://web.dev/articles/cls)
-- [Performance skill](../performance/SKILL.md)
+- [Code Optimizer skill](../code-optimizer/SKILL.md)
diff --git a/src/resources/skills/create-gsd-extension/workflows/debug-extension.md b/src/resources/skills/create-gsd-extension/workflows/debug-extension.md
index ceef023ee..58b1e982e 100644
--- a/src/resources/skills/create-gsd-extension/workflows/debug-extension.md
+++ b/src/resources/skills/create-gsd-extension/workflows/debug-extension.md
@@ -42,7 +42,7 @@ The file must `export default function(pi: ExtensionAPI) { ... }`.
## Step 4: Check for Common Mistakes
-Read `references/key-rules-gotchas.md` and verify each rule against the extension code.
+Read `../references/key-rules-gotchas.md` and verify each rule against the extension code.
## Step 5: Add Debugging
diff --git a/src/resources/skills/github-workflows/SKILL.md b/src/resources/skills/github-workflows/SKILL.md
index dd8e75914..7bb178939 100644
--- a/src/resources/skills/github-workflows/SKILL.md
+++ b/src/resources/skills/github-workflows/SKILL.md
@@ -88,5 +88,3 @@ EVIDENCE: [output from ci_monitor.cjs]
## References
- `references/gh/SKILL.md` — gh CLI reference
-- `scripts/ci_monitor.cjs` — CI monitoring tool
-- `scripts/ci_monitor.md` — Tool usage documentation
diff --git a/src/resources/skills/swiftui/SKILL.md b/src/resources/skills/swiftui/SKILL.md
deleted file mode 100644
index 4ce6f5b00..000000000
--- a/src/resources/skills/swiftui/SKILL.md
+++ /dev/null
@@ -1,208 +0,0 @@
----
-name: swiftui
-description: SwiftUI apps from scratch through App Store. Full lifecycle - create, debug, test, optimize, ship.
----
-
-
-## How We Work
-
-**The user is the product owner. Claude is the developer.**
-
-The user does not write code. The user does not read code. The user describes what they want and judges whether the result is acceptable. Claude implements, verifies, and reports outcomes.
-
-### 1. Prove, Don't Promise
-
-Never say "this should work." Prove it:
-```bash
-xcodebuild build 2>&1 | xcsift # Build passes
-xcodebuild test # Tests pass
-open .../App.app # App launches
-```
-If you didn't run it, you don't know it works.
-
-### 2. Tests for Correctness, Eyes for Quality
-
-| Question | How to Answer |
-|----------|---------------|
-| Does the logic work? | Write test, see it pass |
-| Does it look right? | Launch app, user looks at it |
-| Does it feel right? | User uses it |
-| Does it crash? | Test + launch |
-| Is it fast enough? | Profiler |
-
-Tests verify *correctness*. The user verifies *desirability*.
-
-### 3. Report Outcomes, Not Code
-
-**Bad:** "I refactored the view model to use @Observable with environment injection"
-**Good:** "Fixed the state bug. App now updates correctly when you add items. Ready for you to verify."
-
-The user doesn't care what you changed. The user cares what's different.
-
-### 4. Small Steps, Always Verified
-
-```
-Change → Verify → Report → Next change
-```
-
-Never batch up work. Never say "I made several changes." Each change is verified before the next. If something breaks, you know exactly what caused it.
-
-### 5. Ask Before, Not After
-
-Unclear requirement? Ask now.
-Multiple valid approaches? Ask which.
-Scope creep? Ask if wanted.
-Big refactor needed? Ask permission.
-
-Wrong: Build for 30 minutes, then "is this what you wanted?"
-Right: "Before I start, does X mean Y or Z?"
-
-### 6. Always Leave It Working
-
-Every stopping point = working state. Tests pass, app launches, changes committed. The user can walk away anytime and come back to something that works.
-
-
-
-## SwiftUI Framework Principles
-
-### Declarative Mindset
-Describe what the UI should look like for a given state, not how to mutate it. Let SwiftUI manage the rendering. Never force updates - change the state and let the framework react.
-
-### Single Source of Truth
-Every piece of data has one authoritative location. Use the right property wrapper: @State for view-local, @Observable for shared objects, @Environment for app-wide. Derived data should be computed, not stored.
-
-### Composition Over Inheritance
-Build complex UIs by composing small, focused views. Extract reusable components when patterns emerge. Prefer many small views over few large ones.
-
-### Platform-Adaptive Design
-Write once but respect platform idioms. Use native navigation patterns, respect safe areas, adapt to screen sizes. Test on all target platforms.
-
-
-
-**What would you like to do?**
-
-1. Build a new SwiftUI app
-2. Debug an existing SwiftUI app
-3. Add a feature to an existing app
-4. Write/run tests
-5. Optimize performance
-6. Ship/release to App Store
-7. Something else
-
-**Then read the matching workflow from `workflows/` and follow it.**
-
-
-
-| Response | Workflow |
-|----------|----------|
-| 1, "new", "create", "build", "start" | `workflows/build-new-app.md` |
-| 2, "broken", "fix", "debug", "crash", "bug" | `workflows/debug-swiftui.md` |
-| 3, "add", "feature", "implement", "change" | `workflows/add-feature.md` |
-| 4, "test", "tests", "TDD", "coverage" | `workflows/write-tests.md` |
-| 5, "slow", "optimize", "performance", "fast" | `workflows/optimize-performance.md` |
-| 6, "ship", "release", "deploy", "publish", "app store" | `workflows/ship-app.md` |
-| 7, other | Clarify, then select workflow or references |
-
-
-
-## After Every Change
-
-```bash
-# 1. Does it build?
-xcodebuild -scheme AppName build 2>&1 | xcsift
-
-# 2. Do tests pass? (use Core scheme for SwiftUI apps to avoid @main hang)
-xcodebuild -scheme AppNameCore test
-
-# 3. Does it launch?
-# macOS:
-open ./build/Build/Products/Debug/AppName.app
-
-# iOS Simulator:
-xcrun simctl boot "iPhone 15 Pro" 2>/dev/null || true
-xcrun simctl install booted ./build/Build/Products/Debug-iphonesimulator/AppName.app
-xcrun simctl launch booted com.yourcompany.appname
-```
-
-Note: If tests hang, the test target likely depends on the app target which has `@main`. Extract testable code to a framework target. See `../macos-apps/references/testing-tdd.md` for the pattern.
-
-Report to the user:
-- "Build: ✓"
-- "Tests: 12 pass, 0 fail"
-- "App launches, ready for you to check [specific thing]"
-
-
-
-## CLI Workflow References
-
-For building, debugging, testing, and shipping from CLI without opening Xcode, read these from `../macos-apps/references/`:
-
-| Reference | Use For |
-|-----------|---------|
-| `cli-workflow.md` | Build, run, test commands; xcodebuild usage; code signing |
-| `cli-observability.md` | Log streaming, crash analysis, memory debugging, LLDB |
-| `project-scaffolding.md` | XcodeGen project.yml templates, file structure, entitlements |
-| `testing-tdd.md` | Test patterns that work from CLI, avoiding @main hangs |
-
-These docs are platform-agnostic. For iOS, change destinations:
-```bash
-# iOS Simulator
-xcodebuild -scheme AppName -destination 'platform=iOS Simulator,name=iPhone 15 Pro' build
-
-# macOS
-xcodebuild -scheme AppName build
-```
-
-
-
-## Domain Knowledge
-
-All in `references/`:
-
-**Core:**
-- architecture.md - MVVM patterns, project structure, dependency injection
-- state-management.md - Property wrappers, @Observable, data flow
-- layout-system.md - Stacks, grids, GeometryReader, custom layouts
-
-**Navigation & Animation:**
-- navigation.md - NavigationStack, sheets, tabs, deep linking
-- animations.md - Built-in animations, transitions, matchedGeometryEffect
-
-**Data & Platform:**
-- swiftdata.md - Persistence, @Model, @Query, CloudKit sync
-- platform-integration.md - iOS/macOS/watchOS/visionOS specifics
-- uikit-appkit-interop.md - UIViewRepresentable, hosting controllers
-
-**Support:**
-- networking-async.md - async/await, .task modifier, API clients
-- testing-debugging.md - Previews, unit tests, UI tests, debugging
-- performance.md - Profiling, lazy loading, view identity
-
-
-
-## Workflows
-
-All in `workflows/`:
-
-| Workflow | Purpose |
-|----------|---------|
-| build-new-app.md | Create new SwiftUI app from scratch |
-| debug-swiftui.md | Find and fix SwiftUI bugs |
-| add-feature.md | Add functionality to existing app |
-| write-tests.md | Write UI and unit tests |
-| optimize-performance.md | Profile and improve performance |
-| ship-app.md | App Store submission, TestFlight, distribution |
-
-
-
-## Terminology
-
-Use these terms consistently:
-- **view** (not: widget, component, element)
-- **@Observable** (not: ObservableObject, @Published for new iOS 17+ code)
-- **NavigationStack** (not: NavigationView - deprecated)
-- **SwiftData** (not: Core Data for new projects)
-- **@Environment** (not: @EnvironmentObject for new code)
-- **modifier** (not: method/function when describing view modifiers)
-- **body** (not: render/build when describing view body)
-
diff --git a/src/resources/skills/swiftui/references/animations.md b/src/resources/skills/swiftui/references/animations.md
deleted file mode 100644
index 0d2d84e8d..000000000
--- a/src/resources/skills/swiftui/references/animations.md
+++ /dev/null
@@ -1,921 +0,0 @@
-
-SwiftUI animations are declarative and state-driven. When state changes, SwiftUI automatically animates views from old to new values. Your role is to control timing curves, duration, and which state changes trigger animations.
-
-Key insight: Animations are automatic when state changes - you control timing/curve, not the mechanics.
-
-This file covers:
-- Implicit vs explicit animations
-- Spring animations (iOS 17+ duration/bounce API)
-- Transitions for appearing/disappearing views
-- matchedGeometryEffect for hero animations
-- PhaseAnimator and KeyframeAnimator (iOS 17+)
-- Gesture-driven animations
-
-See also:
-- navigation.md for NavigationStack transitions
-- performance.md for animation optimization strategies
-
-
-
-## Implicit Animations (.animation modifier)
-
-Implicit animations apply whenever an animatable property changes on a view. Always specify which value triggers the animation using the `value:` parameter to prevent unexpected animations.
-
-**Basic usage:**
-```swift
-struct ContentView: View {
- @State private var scale: CGFloat = 1.0
-
- var body: some View {
- Circle()
- .fill(.blue)
- .scaleEffect(scale)
- .animation(.spring(), value: scale)
- .onTapGesture {
- scale = scale == 1.0 ? 1.5 : 1.0
- }
- }
-}
-```
-
-**Animation types:**
-- `.default` - System default spring animation
-- `.linear(duration:)` - Constant speed from start to finish
-- `.easeIn(duration:)` - Starts slow, accelerates
-- `.easeOut(duration:)` - Starts fast, decelerates
-- `.easeInOut(duration:)` - Slow start and end, fast middle
-- `.spring()` - iOS 17+ spring with default parameters
-- `.bouncy` - Preset spring with high bounce
-- `.snappy` - Preset spring with quick, slight bounce
-- `.smooth` - Preset spring with no bounce
-
-**Value-specific animation:**
-```swift
-struct MultiPropertyView: View {
- @State private var rotation: Double = 0
- @State private var scale: CGFloat = 1.0
-
- var body: some View {
- Rectangle()
- .fill(.red)
- .scaleEffect(scale)
- .rotationEffect(.degrees(rotation))
- .animation(.spring(), value: rotation) // Only animate rotation
- .animation(.easeInOut, value: scale) // Different animation for scale
- }
-}
-```
-
-**Why always use value: parameter:**
-- Prevents unexpected animations on unrelated state changes
-- Device rotation won't trigger animations
-- More predictable behavior
-- Better performance (only tracks specific value)
-
-
-
-## Explicit Animations (withAnimation)
-
-Explicit animations only affect properties that depend on values changed inside the `withAnimation` closure. Preferred for user-triggered actions.
-
-**Basic usage:**
-```swift
-struct ContentView: View {
- @State private var isExpanded = false
-
- var body: some View {
- VStack {
- if isExpanded {
- Text("Details")
- .transition(.opacity)
- }
-
- Button("Toggle") {
- withAnimation(.spring()) {
- isExpanded.toggle()
- }
- }
- }
- }
-}
-```
-
-**Completion handlers (iOS 17+):**
-```swift
-Button("Animate") {
- withAnimation(.easeInOut(duration: 1.0)) {
- offset.y = 200
- } completion: {
- // Animation finished - safe to perform next action
- showNextStep = true
- }
-}
-```
-
-**Transaction-based:**
-```swift
-var transaction = Transaction(animation: .spring())
-transaction.disablesAnimations = true // Temporarily disable animations
-
-withTransaction(transaction) {
- someState.toggle()
-}
-```
-
-**Removing animations temporarily:**
-```swift
-withAnimation(nil) {
- // Changes happen immediately without animation
- resetState()
-}
-```
-
-
-
-## Spring Animations
-
-Springs are the default animation in SwiftUI. They feel natural because they mimic real-world physics.
-
-**Modern spring parameters (iOS 17+):**
-```swift
-// Duration and bounce control
-.spring(duration: 0.5, bounce: 0.3)
-
-// No bounce with blend duration for smooth transitions
-.spring(duration: 0.5, bounce: 0, blendDuration: 0.2)
-
-// With initial velocity for gesture-driven animations
-.spring(duration: 0.6, bounce: 0.4)
-```
-
-**Bounce parameter:**
-- `-1.0` to `1.0` range
-- `0` = no bounce (critically damped)
-- `0.3` to `0.5` = natural bounce
-- `0.7` to `1.0` = exaggerated bounce
-- Negative values create "anticipation" (overshoots in opposite direction first)
-
-**Presets (iOS 17+):**
-```swift
-.bouncy // High bounce - playful, attention-grabbing
-.snappy // Quick with slight bounce - feels responsive
-.smooth // No bounce - elegant, sophisticated
-```
-
-**Tuning workflow:**
-1. Start with duration that feels right
-2. Adjust bounce to set character/feeling
-3. Use presets first, then customize if needed
-
-**Legacy spring (still works):**
-```swift
-// For backward compatibility or precise control
-.spring(response: 0.5, dampingFraction: 0.7, blendDuration: 0)
-```
-
-**When to use springs:**
-- User interactions (button presses, drags)
-- Most UI state changes
-- Default choice unless you need precise timing
-
-
-
-## Transitions
-
-Transitions control how views appear and disappear. Applied with `.transition()` modifier, animated by wrapping insertion/removal in `withAnimation`.
-
-**Built-in transitions:**
-```swift
-struct TransitionsDemo: View {
- @State private var showDetail = false
-
- var body: some View {
- VStack {
- if showDetail {
- Text("Detail")
- .transition(.opacity) // Fade in/out
- // .transition(.slide) // Slide from leading edge
- // .transition(.scale) // Grow/shrink from center
- // .transition(.move(edge: .bottom)) // Slide from bottom
- // .transition(.push(from: .leading)) // Push from leading (iOS 16+)
- }
-
- Button("Toggle") {
- withAnimation {
- showDetail.toggle()
- }
- }
- }
- }
-}
-```
-
-**Combining transitions:**
-```swift
-// Both opacity and scale together
-.transition(.opacity.combined(with: .scale))
-
-// Different insertion and removal
-.transition(.asymmetric(
- insertion: .move(edge: .leading).combined(with: .opacity),
- removal: .move(edge: .trailing).combined(with: .opacity)
-))
-```
-
-**Custom transitions:**
-```swift
-struct RotateModifier: ViewModifier {
- let rotation: Double
-
- func body(content: Content) -> some View {
- content
- .rotationEffect(.degrees(rotation))
- .opacity(rotation == 0 ? 1 : 0)
- }
-}
-
-extension AnyTransition {
- static var pivot: AnyTransition {
- .modifier(
- active: RotateModifier(rotation: -90),
- identity: RotateModifier(rotation: 0)
- )
- }
-}
-
-// Usage
-Text("Pivoting in")
- .transition(.pivot)
-```
-
-**Identity vs insertion/removal:**
-- `identity` = final state when view is visible
-- `active` = state during transition (appearing/disappearing)
-
-
-
-## matchedGeometryEffect
-
-Synchronizes geometry between two views with the same ID, creating hero animations. Views don't need to be in the same container.
-
-**Basic hero animation:**
-```swift
-struct HeroDemo: View {
- @State private var isExpanded = false
- @Namespace private var animation
-
- var body: some View {
- VStack {
- if !isExpanded {
- // Thumbnail state
- Circle()
- .fill(.blue)
- .frame(width: 60, height: 60)
- .matchedGeometryEffect(id: "circle", in: animation)
- .onTapGesture {
- withAnimation(.spring()) {
- isExpanded = true
- }
- }
- } else {
- // Expanded state
- VStack {
- Circle()
- .fill(.blue)
- .frame(width: 200, height: 200)
- .matchedGeometryEffect(id: "circle", in: animation)
-
- Button("Close") {
- withAnimation(.spring()) {
- isExpanded = false
- }
- }
- }
- }
- }
- }
-}
-```
-
-**Creating namespace:**
-```swift
-@Namespace private var animation // Property wrapper creates unique namespace
-```
-
-**isSource parameter:**
-Controls which view provides geometry during transition.
-
-```swift
-// Example: Grid to detail view
-struct ContentView: View {
- @State private var selectedItem: Item?
- @Namespace private var namespace
-
- var body: some View {
- ZStack {
- // Grid view
- LazyVGrid(columns: columns) {
- ForEach(items) { item in
- ItemCard(item: item)
- .matchedGeometryEffect(
- id: item.id,
- in: namespace,
- isSource: selectedItem == nil // Source when detail not shown
- )
- .onTapGesture {
- selectedItem = item
- }
- }
- }
-
- // Detail view
- if let item = selectedItem {
- DetailView(item: item)
- .matchedGeometryEffect(
- id: item.id,
- in: namespace,
- isSource: selectedItem != nil // Source when detail shown
- )
- }
- }
- .animation(.spring(), value: selectedItem)
- }
-}
-```
-
-**Properties parameter:**
-Control what gets matched.
-
-```swift
-.matchedGeometryEffect(
- id: "shape",
- in: namespace,
- properties: .frame // Only match frame, not position
-)
-
-// Options: .frame, .position, .size
-```
-
-**Common pitfalls:**
-- **Both views must exist simultaneously** during animation - use conditional rendering carefully
-- **Same ID required** - use stable identifiers (UUIDs, database IDs)
-- **Need explicit animation** - wrap state changes in `withAnimation`
-- **ZStack coordination** - often need ZStack to ensure both views render during transition
-
-
-
-## Phased Animations (iOS 17+)
-
-PhaseAnimator automatically cycles through animation phases. Ideal for loading indicators, attention-grabbing effects, or multi-step sequences.
-
-**PhaseAnimator with continuous cycling:**
-```swift
-struct PulsingCircle: View {
- var body: some View {
- PhaseAnimator([false, true]) { isLarge in
- Circle()
- .fill(.red)
- .scaleEffect(isLarge ? 1.5 : 1.0)
- .opacity(isLarge ? 0.5 : 1.0)
- } animation: { phase in
- .easeInOut(duration: 1.0)
- }
- }
-}
-```
-
-**PhaseAnimator with enum phases:**
-```swift
-enum LoadingPhase: CaseIterable {
- case initial, loading, success
-
- var scale: CGFloat {
- switch self {
- case .initial: 1.0
- case .loading: 1.2
- case .success: 1.5
- }
- }
-
- var color: Color {
- switch self {
- case .initial: .gray
- case .loading: .blue
- case .success: .green
- }
- }
-}
-
-struct LoadingButton: View {
- var body: some View {
- PhaseAnimator(LoadingPhase.allCases) { phase in
- Circle()
- .fill(phase.color)
- .scaleEffect(phase.scale)
- } animation: { phase in
- switch phase {
- case .initial: .easeIn(duration: 0.3)
- case .loading: .easeInOut(duration: 0.5)
- case .success: .spring(duration: 0.6, bounce: 0.4)
- }
- }
- }
-}
-```
-
-**Trigger-based PhaseAnimator:**
-```swift
-struct TriggerDemo: View {
- @State private var triggerValue = 0
-
- var body: some View {
- VStack {
- PhaseAnimator([0, 1, 2], trigger: triggerValue) { phase in
- RoundedRectangle(cornerRadius: 12)
- .fill(.blue)
- .frame(width: 100 + CGFloat(phase * 50), height: 100)
- .offset(x: CGFloat(phase * 20))
- }
-
- Button("Animate") {
- triggerValue += 1
- }
- }
- }
-}
-```
-
-**Use cases:**
-- Loading spinners and progress indicators
-- Attention-grabbing call-to-action buttons
-- Celebratory success animations
-- Idle state animations
-- Tutorial highlights
-
-
-
-## Keyframe Animations (iOS 17+)
-
-KeyframeAnimator provides frame-by-frame control over complex animations. More powerful than PhaseAnimator when you need precise timing and multiple simultaneous property changes.
-
-**Basic KeyframeAnimator:**
-```swift
-struct AnimationValues {
- var scale = 1.0
- var rotation = 0.0
- var opacity = 1.0
-}
-
-struct KeyframeDemo: View {
- @State private var trigger = false
-
- var body: some View {
- KeyframeAnimator(
- initialValue: AnimationValues(),
- trigger: trigger
- ) { values in
- Rectangle()
- .fill(.purple)
- .scaleEffect(values.scale)
- .rotationEffect(.degrees(values.rotation))
- .opacity(values.opacity)
- .frame(width: 100, height: 100)
- } keyframes: { _ in
- KeyframeTrack(\.scale) {
- SpringKeyframe(1.5, duration: 0.3)
- CubicKeyframe(0.8, duration: 0.2)
- CubicKeyframe(1.0, duration: 0.2)
- }
-
- KeyframeTrack(\.rotation) {
- LinearKeyframe(180, duration: 0.4)
- CubicKeyframe(360, duration: 0.3)
- }
-
- KeyframeTrack(\.opacity) {
- CubicKeyframe(0.5, duration: 0.3)
- CubicKeyframe(1.0, duration: 0.4)
- }
- }
- .onTapGesture {
- trigger.toggle()
- }
- }
-}
-```
-
-**Keyframe types:**
-
-```swift
-// Linear - constant speed interpolation
-LinearKeyframe(targetValue, duration: 0.5)
-
-// Cubic - smooth Bezier curve
-CubicKeyframe(targetValue, duration: 0.5)
-
-// Spring - physics-based bounce
-SpringKeyframe(targetValue, duration: 0.5, spring: .bouncy)
-
-// Move - jump immediately to value
-MoveKeyframe(targetValue)
-```
-
-**Complex multi-property animation:**
-```swift
-struct AnimationState {
- var position: CGPoint = .zero
- var color: Color = .blue
- var size: CGFloat = 50
-}
-
-KeyframeAnimator(initialValue: AnimationState(), trigger: animate) { state in
- Circle()
- .fill(state.color)
- .frame(width: state.size, height: state.size)
- .position(state.position)
-} keyframes: { _ in
- KeyframeTrack(\.position) {
- CubicKeyframe(CGPoint(x: 200, y: 100), duration: 0.4)
- SpringKeyframe(CGPoint(x: 200, y: 300), duration: 0.6)
- CubicKeyframe(CGPoint(x: 0, y: 0), duration: 0.5)
- }
-
- KeyframeTrack(\.color) {
- CubicKeyframe(.red, duration: 0.5)
- CubicKeyframe(.green, duration: 0.5)
- CubicKeyframe(.blue, duration: 0.5)
- }
-
- KeyframeTrack(\.size) {
- SpringKeyframe(100, duration: 0.6, spring: .bouncy)
- CubicKeyframe(50, duration: 0.4)
- }
-}
-```
-
-**When to use KeyframeAnimator:**
-- Complex choreographed animations
-- Precise timing control needed
-- Multiple properties animating with different curves
-- Path-based animations
-- Recreating motion design prototypes
-
-
-
-## Gesture-Driven Animations
-
-Interactive animations that respond to user input in real-time.
-
-**DragGesture with spring animation:**
-```swift
-struct DraggableCard: View {
- @State private var offset: CGSize = .zero
-
- var body: some View {
- RoundedRectangle(cornerRadius: 20)
- .fill(.blue)
- .frame(width: 200, height: 300)
- .offset(offset)
- .gesture(
- DragGesture()
- .onChanged { value in
- offset = value.translation
- }
- .onEnded { _ in
- withAnimation(.spring(duration: 0.5, bounce: 0.3)) {
- offset = .zero
- }
- }
- )
- }
-}
-```
-
-**Interruptible animations:**
-```swift
-struct InterruptibleView: View {
- @State private var position: CGFloat = 0
-
- var body: some View {
- Circle()
- .fill(.red)
- .frame(width: 60, height: 60)
- .offset(y: position)
- .animation(.spring(), value: position)
- .gesture(
- DragGesture()
- .onChanged { value in
- // Interrupts ongoing animation immediately
- position = value.translation.height
- }
- .onEnded { value in
- // Determine snap point based on velocity
- let velocity = value.predictedEndLocation.y - value.location.y
-
- if abs(velocity) > 500 {
- position = velocity > 0 ? 300 : -300
- } else {
- position = 0
- }
- }
- )
- }
-}
-```
-
-**GestureState for automatic reset:**
-```swift
-struct GestureStateExample: View {
- @GestureState private var dragOffset: CGSize = .zero
- @State private var permanentOffset: CGSize = .zero
-
- var body: some View {
- Rectangle()
- .fill(.purple)
- .frame(width: 100, height: 100)
- .offset(x: permanentOffset.width + dragOffset.width,
- y: permanentOffset.height + dragOffset.height)
- .gesture(
- DragGesture()
- .updating($dragOffset) { value, state, _ in
- state = value.translation
- }
- .onEnded { value in
- withAnimation(.spring()) {
- permanentOffset.width += value.translation.width
- permanentOffset.height += value.translation.height
- }
- }
- )
- }
-}
-```
-
-**Combining gestures with animations:**
-```swift
-struct SwipeToDelete: View {
- @State private var offset: CGFloat = 0
- @State private var isDeleted = false
-
- var body: some View {
- if !isDeleted {
- HStack {
- Text("Swipe to delete")
- Spacer()
- }
- .padding()
- .background(.white)
- .offset(x: offset)
- .gesture(
- DragGesture()
- .onChanged { value in
- if value.translation.width < 0 {
- offset = value.translation.width
- }
- }
- .onEnded { value in
- if offset < -100 {
- withAnimation(.easeOut(duration: 0.3)) {
- offset = -500
- } completion: {
- isDeleted = true
- }
- } else {
- withAnimation(.spring()) {
- offset = 0
- }
- }
- }
- )
- }
- }
-}
-```
-
-**Velocity-based animations:**
-```swift
-struct VelocityDrag: View {
- @State private var offset: CGSize = .zero
-
- var body: some View {
- Circle()
- .fill(.green)
- .frame(width: 80, height: 80)
- .offset(offset)
- .gesture(
- DragGesture()
- .onChanged { value in
- offset = value.translation
- }
- .onEnded { value in
- let velocity = value.velocity
-
- // Use velocity magnitude to determine spring response
- let speed = sqrt(velocity.width * velocity.width +
- velocity.height * velocity.height)
-
- let animation: Animation = speed > 1000
- ? .spring(duration: 0.4, bounce: 0.5)
- : .spring(duration: 0.6, bounce: 0.3)
-
- withAnimation(animation) {
- offset = .zero
- }
- }
- )
- }
-}
-```
-
-
-
-## Choosing the Right Animation
-
-**Simple state change:**
-- Use `.animation(.default, value: state)` for single property changes
-- Implicit animation is simplest approach
-
-**User-triggered change:**
-- Use `withAnimation { }` for button taps, user actions
-- Explicit animation provides better control
-- Use completion handlers (iOS 17+) for sequential actions
-
-**View appearing/disappearing:**
-- Use `.transition()` for conditional views
-- Combine with `withAnimation` to trigger
-- Consider `.asymmetric()` for different in/out animations
-
-**Shared element between screens:**
-- Use `matchedGeometryEffect` for hero animations
-- Requires both views to exist during transition
-- Best with `@Namespace` and explicit animations
-
-**Multi-step sequence:**
-- Use `PhaseAnimator` (iOS 17+) for simple phase-based sequences
-- Great for loading states, idle animations
-- Trigger-based for user-initiated sequences
-
-**Complex keyframed motion:**
-- Use `KeyframeAnimator` (iOS 17+) for precise timing
-- Multiple properties with independent curves
-- Recreating motion design specs
-
-**User-controlled motion:**
-- Use `DragGesture` + animation for interactive elements
-- `@GestureState` for automatic state reset
-- Consider velocity for natural physics
-
-**Performance tips:**
-- Animate opacity, scale, offset (cheap)
-- Avoid animating frame size, padding (expensive)
-- Use `.drawingGroup()` for complex hierarchies being animated
-- Avoid animating during scroll (competes with scroll performance)
-- Profile with Instruments if animations drop frames
-
-
-
-## What NOT to Do
-
-
-**Problem:**
-```swift
-.animation(.spring()) // No value parameter
-```
-
-**Why it's bad:**
-Animates every property change, including device rotation, parent view updates, and unrelated state changes. Creates unexpected animations and performance issues.
-
-**Instead:**
-```swift
-.animation(.spring(), value: specificState)
-```
-
-
-
-**Problem:**
-```swift
-withAnimation {
- frameWidth = 300 // Triggers layout recalculation
- padding = 20 // Triggers layout recalculation
-}
-```
-
-**Why it's bad:**
-Frame size and padding changes force SwiftUI to recalculate layout, which is expensive. Can cause stuttering on complex views.
-
-**Instead:**
-```swift
-withAnimation {
- scale = 1.5 // Cheap transform
- opacity = 0.5 // Cheap property
- offset = CGSize(width: 20, height: 0) // Cheap transform
-}
-```
-
-
-
-**Problem:**
-```swift
-Circle()
- .matchedGeometryEffect(id: "circle", in: ???) // Forgot @Namespace
-```
-
-**Why it's bad:**
-Won't compile. Namespace is required to coordinate geometry matching.
-
-**Instead:**
-```swift
-@Namespace private var animation
-
-Circle()
- .matchedGeometryEffect(id: "circle", in: animation)
-```
-
-
-
-**Problem:**
-```swift
-withAnimation(.easeIn) {
- withAnimation(.spring()) {
- state = newValue
- }
-}
-```
-
-**Why it's bad:**
-Inner animation is ignored. Only outer animation applies. Creates confusion about which animation runs.
-
-**Instead:**
-```swift
-withAnimation(.spring()) {
- state = newValue
-}
-```
-
-
-
-**Problem:**
-```swift
-if showDetail {
- DetailView()
- .transition(.slide) // Transition defined but not triggered
-}
-```
-
-**Why it's bad:**
-View appears/disappears instantly. Transition is never applied without animation context.
-
-**Instead:**
-```swift
-Button("Toggle") {
- withAnimation {
- showDetail.toggle()
- }
-}
-```
-
-
-
-**Problem:**
-```swift
-var computedValue: Double {
- return stateA * stateB
-}
-
-.animation(.spring(), value: computedValue)
-```
-
-**Why it's bad:**
-Computed properties can change for many reasons. Animation triggers on any dependency change, not just intentional updates.
-
-**Instead:**
-```swift
-.animation(.spring(), value: stateA)
-.animation(.spring(), value: stateB)
-```
-
-
-
-**Problem:**
-```swift
-// Both views exist at same time with same ID
-GridItem()
- .matchedGeometryEffect(id: item.id, in: namespace)
-
-DetailItem()
- .matchedGeometryEffect(id: item.id, in: namespace)
-```
-
-**Why it's bad:**
-Without proper `isSource` configuration, SwiftUI doesn't know which view's geometry to use. Creates unpredictable animations.
-
-**Instead:**
-```swift
-GridItem()
- .matchedGeometryEffect(id: item.id, in: namespace, isSource: selectedItem == nil)
-
-DetailItem()
- .matchedGeometryEffect(id: item.id, in: namespace, isSource: selectedItem != nil)
-```
-
-
diff --git a/src/resources/skills/swiftui/references/architecture.md b/src/resources/skills/swiftui/references/architecture.md
deleted file mode 100644
index 84c800559..000000000
--- a/src/resources/skills/swiftui/references/architecture.md
+++ /dev/null
@@ -1,1561 +0,0 @@
-
-SwiftUI architecture determines how you organize code, manage dependencies, and structure your app for maintainability and scalability. This file covers architectural patterns, project organization, and design decisions.
-
-**Read this when:**
-- Starting a new SwiftUI project
-- Deciding between MVVM, TCA, or other patterns
-- Structuring a growing codebase
-- Setting up dependency injection
-- Organizing features into modules
-
-**Related files:**
-- state-management.md - State ownership and data flow within architectures
-- navigation.md - Navigation patterns for different architectures
-- networking-async.md - Async operations and where they fit architecturally
-- swiftdata.md - Persistence layer integration with architecture
-
-
-
-## Available Approaches
-
-
-
-
-
-
-
-
-
-
-
-## Choosing the Right Approach
-
-**If building a simple app (under 10 screens, minimal business logic):** Use SwiftUI Native because architectural overhead isn't justified and SwiftUI's built-in patterns are sufficient.
-
-**If using SwiftData extensively:** Use SwiftUI Native or consider Clean Architecture. Avoid MVVM because @Query requires views to manage data directly, conflicting with ViewModel patterns.
-
-**If you need testability and moderate complexity (10-30 screens):** Use MVVM with @Observable because it provides clean separation, is industry-standard, and offers excellent testability with minimal overhead.
-
-**If you have complex state management, navigation, or side effects:** Consider The Composable Architecture because its unidirectional data flow and built-in effect handling excel at managing complexity.
-
-**If building a large app with multiple teams:** Use Clean Architecture with Feature Modules because it enforces boundaries, enables parallel development, and improves build times through modularization.
-
-**If team is unfamiliar with iOS architectures:** Start with MVVM because it's the most widely understood pattern and has abundant learning resources.
-
-**If prototyping or validating product-market fit:** Use SwiftUI Native because you can ship fastest and refactor to MVVM or Clean Architecture later when requirements stabilize.
-
-**Default recommendation:** MVVM with @Observable for most production apps because it balances simplicity, testability, and scalability. Migrate to Clean Architecture with modules only when team size or app complexity demands it.
-
-**Avoid MVVM when:** Using SwiftData heavily, or building very simple apps where the architectural overhead slows development without providing value.
-
-**Avoid TCA when:** Team lacks Redux experience, building simple CRUD apps, or working in large multi-team environments where TCA's scaling limitations may surface.
-
-
-
-## Common Patterns
-
-
-**Use when:** Need compile-time safe dependency injection without manual container setup
-
-Factory is the current recommended DI library for Swift (2024). Import the library as "FactoryKit" to avoid naming conflicts.
-
-**Implementation:**
-```swift
-// 1. Install Factory via SPM
-// https://github.com/hmlongco/Factory
-// Add package, select "FactoryKit" library
-
-// 2. Define container with factories
-import FactoryKit
-
-extension Container {
- var apiClient: Factory {
- Factory(self) { APIClient(baseURL: "https://api.example.com") }
- }
-
- var userRepository: Factory {
- Factory(self) { UserRepository(apiClient: self.apiClient()) }
- }
-
- var userListViewModel: Factory {
- Factory(self) {
- UserListViewModel(userRepository: self.userRepository())
- }
- .scope(.shared) // Singleton if needed
- }
-}
-
-// 3. Inject in ViewModels using @Injected
-@Observable
-@MainActor
-class UserListViewModel {
- @ObservationIgnored @Injected(\.userRepository)
- private var userRepository: UserRepositoryProtocol
-
- var users: [User] = []
-
- func loadUsers() async {
- users = try await userRepository.fetchUsers()
- }
-}
-
-// 4. Inject in Views using @Injected
-struct UserListView: View {
- @State private var viewModel = Container.shared.userListViewModel()
-
- var body: some View {
- List(viewModel.users) { user in
- Text(user.name)
- }
- }
-}
-
-// 5. Override for testing
-extension Container {
- var mockUserRepository: Factory {
- Factory(self) { MockUserRepository() }
- }
-}
-```
-
-**Considerations:**
-- Use @ObservationIgnored for @Injected properties inside @Observable classes
-- Factory 2.5+ supports Swift 6 strict concurrency
-- Scopes: .singleton, .shared, .cached, .graph, .unique
-- Register mock factories in test targets for easy testing
-
-
-
-**Use when:** Need SwiftUI-native dependency injection without external libraries
-
-**Implementation:**
-```swift
-// 1. Define dependency key
-struct UserRepositoryKey: EnvironmentKey {
- static let defaultValue: UserRepositoryProtocol = UserRepository()
-}
-
-extension EnvironmentValues {
- var userRepository: UserRepositoryProtocol {
- get { self[UserRepositoryKey.self] }
- set { self[UserRepositoryKey.self] = newValue }
- }
-}
-
-// 2. Provide dependency at app root
-@main
-struct MyApp: App {
- var body: some Scene {
- WindowGroup {
- ContentView()
- .environment(\.userRepository, UserRepository())
- }
- }
-}
-
-// 3. Inject in ViewModels
-@Observable
-@MainActor
-class UserListViewModel {
- var users: [User] = []
- private let userRepository: UserRepositoryProtocol
-
- init(userRepository: UserRepositoryProtocol) {
- self.userRepository = userRepository
- }
-
- func loadUsers() async {
- users = try await userRepository.fetchUsers()
- }
-}
-
-// 4. Access in Views
-struct UserListView: View {
- @Environment(\.userRepository) private var userRepository
- @State private var viewModel: UserListViewModel
-
- init() {
- // Can't access @Environment in init - use onAppear workaround
- _viewModel = State(initialValue: UserListViewModel(
- userRepository: UserRepository() // temporary
- ))
- }
-
- var body: some View {
- List(viewModel.users) { user in
- Text(user.name)
- }
- .onAppear {
- // Replace with environment-injected dependency
- viewModel = UserListViewModel(userRepository: userRepository)
- Task { await viewModel.loadUsers() }
- }
- }
-}
-```
-
-**Considerations:**
-- SwiftUI-native approach (no external dependencies)
-- Can't access @Environment in init - requires workaround
-- Better for simple apps; Factory scales better for complex DI
-
-
-
-**Use when:** Abstracting data sources (API, database, cache) from business logic
-
-**Implementation:**
-```swift
-// Protocol in Domain layer
-protocol UserRepositoryProtocol {
- func fetchUsers() async throws -> [User]
- func getUser(id: UUID) async throws -> User
- func saveUser(_ user: User) async throws
- func deleteUser(id: UUID) async throws
-}
-
-// Implementation in Data layer
-class UserRepository: UserRepositoryProtocol {
- private let apiClient: APIClient
- private let cache: CacheService
-
- init(apiClient: APIClient, cache: CacheService) {
- self.apiClient = apiClient
- self.cache = cache
- }
-
- func fetchUsers() async throws -> [User] {
- // Check cache first
- if let cached: [User] = cache.get(key: "users") {
- return cached
- }
-
- // Fetch from API
- let dtos: [UserDTO] = try await apiClient.get("/users")
- let users = dtos.map { $0.toDomain() }
-
- // Update cache
- cache.set(key: "users", value: users)
-
- return users
- }
-
- func getUser(id: UUID) async throws -> User {
- let dto: UserDTO = try await apiClient.get("/users/\(id)")
- return dto.toDomain()
- }
-
- func saveUser(_ user: User) async throws {
- let dto = UserDTO(from: user)
- try await apiClient.post("/users", body: dto)
-
- // Invalidate cache
- cache.remove(key: "users")
- }
-
- func deleteUser(id: UUID) async throws {
- try await apiClient.delete("/users/\(id)")
- cache.remove(key: "users")
- }
-}
-
-// DTO for API mapping
-struct UserDTO: Codable {
- let id: UUID
- let name: String
- let email: String
-
- func toDomain() -> User {
- User(id: id, name: name, email: email)
- }
-
- init(from user: User) {
- self.id = user.id
- self.name = user.name
- self.email = user.email
- }
-}
-```
-
-**Considerations:**
-- Repository owns caching strategy
-- DTOs map between API and domain models
-- Protocol enables easy mocking for tests
-- Keeps networking details out of ViewModels
-
-
-
-**Use when:** Need to toggle features without app updates
-
-**Implementation:**
-```swift
-// Feature flag service
-@Observable
-@MainActor
-class FeatureFlagService {
- private(set) var flags: [String: Bool] = [:]
-
- func isEnabled(_ feature: FeatureFlag) -> Bool {
- flags[feature.rawValue] ?? feature.defaultValue
- }
-
- func enable(_ feature: FeatureFlag) {
- flags[feature.rawValue] = true
- }
-
- func disable(_ feature: FeatureFlag) {
- flags[feature.rawValue] = false
- }
-
- func loadRemoteFlags() async {
- // Fetch from remote config service
- // Update flags dictionary
- }
-}
-
-enum FeatureFlag: String {
- case newUserProfile = "new_user_profile"
- case darkModeToggle = "dark_mode_toggle"
- case experimentalSearch = "experimental_search"
-
- var defaultValue: Bool {
- switch self {
- case .newUserProfile: return false
- case .darkModeToggle: return true
- case .experimentalSearch: return false
- }
- }
-}
-
-// Use in views
-struct ContentView: View {
- @Environment(\.featureFlags) private var featureFlags
-
- var body: some View {
- VStack {
- if featureFlags.isEnabled(.newUserProfile) {
- NewUserProfileView()
- } else {
- LegacyUserProfileView()
- }
- }
- }
-}
-
-// Environment setup
-struct FeatureFlagKey: EnvironmentKey {
- static let defaultValue = FeatureFlagService()
-}
-
-extension EnvironmentValues {
- var featureFlags: FeatureFlagService {
- get { self[FeatureFlagKey.self] }
- set { self[FeatureFlagKey.self] = newValue }
- }
-}
-```
-
-**Considerations:**
-- Load remote flags at app startup
-- Use @MainActor for thread-safe access
-- Feature flags enable A/B testing and gradual rollouts
-
-
-
-**Use when:** Need centralized navigation control separate from views
-
-**Implementation:**
-```swift
-// Navigation coordinator
-@Observable
-@MainActor
-class AppCoordinator {
- var navigationPath = NavigationPath()
- var presentedSheet: SheetDestination?
-
- func push(_ destination: Destination) {
- navigationPath.append(destination)
- }
-
- func pop() {
- navigationPath.removeLast()
- }
-
- func popToRoot() {
- navigationPath = NavigationPath()
- }
-
- func present(_ sheet: SheetDestination) {
- presentedSheet = sheet
- }
-
- func dismiss() {
- presentedSheet = nil
- }
-}
-
-enum Destination: Hashable {
- case userDetail(User)
- case settings
- case editProfile
-}
-
-enum SheetDestination: Identifiable {
- case addUser
- case filter
-
- var id: String {
- switch self {
- case .addUser: return "addUser"
- case .filter: return "filter"
- }
- }
-}
-
-// Root view with coordinator
-struct RootView: View {
- @State private var coordinator = AppCoordinator()
-
- var body: some View {
- NavigationStack(path: $coordinator.navigationPath) {
- UserListView(coordinator: coordinator)
- .navigationDestination(for: Destination.self) { destination in
- switch destination {
- case .userDetail(let user):
- UserDetailView(user: user, coordinator: coordinator)
- case .settings:
- SettingsView(coordinator: coordinator)
- case .editProfile:
- EditProfileView(coordinator: coordinator)
- }
- }
- }
- .sheet(item: $coordinator.presentedSheet) { sheet in
- switch sheet {
- case .addUser:
- AddUserView(coordinator: coordinator)
- case .filter:
- FilterView(coordinator: coordinator)
- }
- }
- }
-}
-
-// Views use coordinator for navigation
-struct UserListView: View {
- let coordinator: AppCoordinator
-
- var body: some View {
- List {
- ForEach(users) { user in
- Button(user.name) {
- coordinator.push(.userDetail(user))
- }
- }
- }
- .toolbar {
- Button("Add") {
- coordinator.present(.addUser)
- }
- }
- }
-}
-```
-
-**Considerations:**
-- Coordinator owns all navigation state
-- Testable - can verify navigation logic independently
-- Works well with deep linking
-- See navigation.md for more patterns
-
-
-
-
-## What NOT to Do
-
-
-**Problem:** Putting all feature logic into a single ViewModel class
-
-```swift
-// DON'T: Massive ViewModel with 1000+ lines
-@Observable
-@MainActor
-class UserViewModel {
- // User list logic
- var users: [User] = []
- func loadUsers() async { }
- func deleteUser() { }
-
- // User detail logic
- var selectedUser: User?
- func loadUserDetails() { }
-
- // Settings logic
- var notificationsEnabled = false
- func saveSettings() { }
-
- // Profile editing logic
- var editedName = ""
- var editedEmail = ""
- func updateProfile() { }
-
- // Search logic
- var searchQuery = ""
- var searchResults: [User] = []
- func search() { }
-
- // ... 900 more lines
-}
-```
-
-**Why it's bad:**
-- Hard to test (must mock entireViewModel for one feature)
-- Poor cohesion (unrelated concerns mixed together)
-- Difficult to navigate and understand
-- High merge conflict risk in teams
-
-**Instead:** Create feature-specific ViewModels
-
-```swift
-// DO: Separate ViewModels per feature
-@Observable
-@MainActor
-class UserListViewModel {
- var users: [User] = []
- var isLoading = false
-
- func loadUsers() async { }
- func deleteUser(_ user: User) { }
-}
-
-@Observable
-@MainActor
-class UserDetailViewModel {
- var user: User
- var isEditing = false
-
- init(user: User) {
- self.user = user
- }
-
- func loadDetails() async { }
-}
-
-@Observable
-@MainActor
-class UserSearchViewModel {
- var query = ""
- var results: [User] = []
-
- func search() async { }
-}
-```
-
-
-
-**Problem:** Still using the old ObservableObject protocol with @Published
-
-```swift
-// DON'T: Old ObservableObject pattern (pre-iOS 17)
-class UserViewModel: ObservableObject {
- @Published var users: [User] = []
- @Published var isLoading = false
- @Published var errorMessage: String?
-}
-
-struct UserListView: View {
- @StateObject private var viewModel = UserViewModel()
- // ...
-}
-```
-
-**Why it's bad:**
-- Worse performance (all @Published changes trigger view updates)
-- More boilerplate (@Published everywhere)
-- Forces use of @StateObject instead of @State
-- Triggers unnecessary view redraws
-
-**Instead:** Use @Observable macro (iOS 17+)
-
-```swift
-// DO: Modern @Observable pattern
-import Observation
-
-@Observable
-@MainActor
-class UserViewModel {
- var users: [User] = []
- var isLoading = false
- var errorMessage: String?
-}
-
-struct UserListView: View {
- @State private var viewModel = UserViewModel()
-
- var body: some View {
- // Only redraws when properties accessed in body change
- List(viewModel.users) { user in
- Text(user.name)
- }
- }
-}
-```
-
-**Migration note:** If supporting iOS 16 or earlier, ObservableObject is still required. Use @Observable for iOS 17+ only projects.
-
-
-
-**Problem:** Trying to use @Query inside a ViewModel
-
-```swift
-// DON'T: @Query doesn't work in ViewModels
-@Observable
-@MainActor
-class UserViewModel {
- @Query var users: [User] // ERROR: @Query only works in Views
-}
-```
-
-**Why it's bad:**
-- @Query requires SwiftUI view context
-- Creates compile errors
-- Forces awkward workarounds
-
-**Instead:** Use @Query directly in views or avoid MVVM with SwiftData
-
-```swift
-// DO: Use @Query in views directly
-struct UserListView: View {
- @Query(sort: \User.name) private var users: [User]
-
- var body: some View {
- List(users) { user in
- Text(user.name)
- }
- }
-}
-
-// OR: Use ModelContext directly in ViewModel if you need MVVM
-@Observable
-@MainActor
-class UserViewModel {
- private let modelContext: ModelContext
- var users: [User] = []
-
- init(modelContext: ModelContext) {
- self.modelContext = modelContext
- }
-
- func loadUsers() {
- let descriptor = FetchDescriptor(
- sortBy: [SortDescriptor(\User.name)]
- )
- users = (try? modelContext.fetch(descriptor)) ?? []
- }
-}
-```
-
-**Best approach:** If using SwiftData heavily, prefer SwiftUI Native architecture over MVVM.
-
-
-
-**Problem:** Creating new ViewModel instance on every view redraw
-
-```swift
-// DON'T: Creates new instance every redraw
-struct UserListView: View {
- @State private var viewModel = UserListViewModel() // Wrong!
-
- var body: some View {
- List(viewModel.users) { user in
- Text(user.name)
- }
- }
-}
-```
-
-**Why it seems wrong:** Looks like it creates new instance on every redraw
-
-**Why it's actually correct:** @State caches the instance across redraws. The initializer only runs on first render.
-
-**Key insight:** With @Observable and @State, the apparent anti-pattern is actually the correct pattern. Unlike @StateObject (which requires @escaping closure workaround), @State with @Observable caches the value automatically.
-
-**Still avoid:**
-```swift
-// DON'T: Creating without @State
-struct UserListView: View {
- private let viewModel = UserListViewModel() // Wrong - recreated every time
-}
-
-// DON'T: Using @StateObject with @Observable
-struct UserListView: View {
- @StateObject private var viewModel = UserListViewModel() // Wrong - use @State
-}
-```
-
-**Do:**
-```swift
-// DO: Use @State with @Observable
-struct UserListView: View {
- @State private var viewModel = UserListViewModel()
-}
-
-// DO: Or inject from parent
-struct UserListView: View {
- let viewModel: UserListViewModel
-}
-```
-
-
-
-**Problem:** Trying to access @Environment values in view initializer
-
-```swift
-// DON'T: Can't access @Environment in init
-struct UserListView: View {
- @Environment(\.userRepository) private var userRepository
- @State private var viewModel: UserListViewModel
-
- init() {
- // ERROR: Can't access userRepository here
- _viewModel = State(initialValue: UserListViewModel(
- userRepository: userRepository
- ))
- }
-}
-```
-
-**Why it's bad:**
-- @Environment not available until view is in the hierarchy
-- Causes compile errors or runtime crashes
-- Requires awkward workarounds
-
-**Instead:** Use Factory for DI or pass dependencies explicitly
-
-```swift
-// DO: Use Factory for clean DI
-import FactoryKit
-
-extension Container {
- var userRepository: Factory {
- Factory(self) { UserRepository() }
- }
-}
-
-@Observable
-@MainActor
-class UserListViewModel {
- @ObservationIgnored @Injected(\.userRepository)
- private var userRepository
-}
-
-struct UserListView: View {
- @State private var viewModel = UserListViewModel()
- // userRepository injected automatically by Factory
-}
-
-// OR: Pass from parent that has access
-struct ParentView: View {
- @Environment(\.userRepository) private var userRepository
-
- var body: some View {
- UserListView(
- viewModel: UserListViewModel(userRepository: userRepository)
- )
- }
-}
-```
-
-
-
-**Problem:** Creating one massive dependency container that knows about everything
-
-```swift
-// DON'T: God object with all dependencies
-class AppDependencies {
- let apiClient: APIClient
- let userRepository: UserRepository
- let postRepository: PostRepository
- let authService: AuthService
- let cacheService: CacheService
- let analyticsService: AnalyticsService
- let pushService: PushService
- let locationService: LocationService
- // ... 50 more dependencies
-
- init() {
- // Complex initialization graph
- }
-}
-
-// Passed everywhere
-struct UserListView: View {
- let dependencies: AppDependencies
-}
-```
-
-**Why it's bad:**
-- Views depend on entire app graph (not just what they need)
-- Hard to test (must construct entire AppDependencies)
-- Poor encapsulation
-- Merge conflicts on AppDependencies class
-
-**Instead:** Use Factory with containers or inject specific dependencies
-
-```swift
-// DO: Factory with automatic resolution
-extension Container {
- var userRepository: Factory {
- Factory(self) { UserRepository(apiClient: self.apiClient()) }
- }
-}
-
-@Observable
-@MainActor
-class UserListViewModel {
- @ObservationIgnored @Injected(\.userRepository)
- private var userRepository // Only knows about userRepository
-}
-
-// Or inject specific dependencies
-struct UserListView: View {
- @State private var viewModel: UserListViewModel
-
- init(userRepository: UserRepositoryProtocol) {
- _viewModel = State(initialValue: UserListViewModel(
- userRepository: userRepository
- ))
- }
-}
-```
-
-
-
-
-## Recommended Project Structure
-
-### Small Apps (5-15 screens, single developer)
-
-```
-MyApp/
-├── MyApp.swift # @main App entry point
-├── Models/
-│ ├── User.swift
-│ ├── Post.swift
-│ └── Comment.swift
-├── Views/
-│ ├── UserList/
-│ │ ├── UserListView.swift
-│ │ └── UserRowView.swift
-│ ├── UserDetail/
-│ │ └── UserDetailView.swift
-│ └── Settings/
-│ └── SettingsView.swift
-├── ViewModels/ # If using MVVM
-│ ├── UserListViewModel.swift
-│ └── UserDetailViewModel.swift
-├── Services/
-│ ├── APIClient.swift
-│ └── CacheService.swift
-├── Utilities/
-│ ├── Extensions.swift
-│ └── Constants.swift
-└── Resources/
- ├── Assets.xcassets
- └── Localizable.strings
-```
-
-**Key principles:**
-- Flat structure with minimal nesting
-- Group by feature (UserList, UserDetail) not layer
-- ViewModels folder only if using MVVM
-- Services for shared business logic
-
-### Medium Apps (15-50 screens, 2-5 developers)
-
-```
-MyApp/
-├── MyApp.swift
-├── App/
-│ ├── DependencyContainer.swift
-│ └── AppCoordinator.swift
-├── Features/
-│ ├── Authentication/
-│ │ ├── Views/
-│ │ │ ├── LoginView.swift
-│ │ │ └── SignupView.swift
-│ │ ├── ViewModels/
-│ │ │ └── AuthViewModel.swift
-│ │ └── Models/
-│ │ └── AuthState.swift
-│ ├── UserList/
-│ │ ├── Views/
-│ │ │ ├── UserListView.swift
-│ │ │ └── UserRowView.swift
-│ │ ├── ViewModels/
-│ │ │ └── UserListViewModel.swift
-│ │ └── Models/
-│ │ └── User.swift
-│ ├── UserDetail/
-│ │ ├── Views/
-│ │ ├── ViewModels/
-│ │ └── Models/
-│ └── Settings/
-│ ├── Views/
-│ └── ViewModels/
-├── Core/
-│ ├── Networking/
-│ │ ├── APIClient.swift
-│ │ ├── Endpoint.swift
-│ │ └── NetworkError.swift
-│ ├── Persistence/
-│ │ └── CacheService.swift
-│ ├── Extensions/
-│ │ ├── View+Extensions.swift
-│ │ └── String+Extensions.swift
-│ └── UI/
-│ ├── LoadingView.swift
-│ └── ErrorView.swift
-└── Resources/
- ├── Assets.xcassets
- └── Localizable.strings
-```
-
-**Key principles:**
-- Features folder with clear feature modules
-- Each feature has Views/ViewModels/Models subfolders
-- Core folder for shared infrastructure
-- App folder for composition root
-- Still single-target Xcode project
-
-### Large Apps (50+ screens, 5+ developers, multi-platform)
-
-Use Swift Package Manager with modular architecture:
-
-```
-MyApp/
-├── App/
-│ ├── MyApp/
-│ │ ├── MyApp.swift
-│ │ ├── DependencyContainer.swift
-│ │ └── AppCoordinator.swift
-│ └── MyApp.xcodeproj
-├── Packages/
-│ ├── Domain/
-│ │ ├── Package.swift
-│ │ └── Sources/Domain/
-│ │ ├── Models/
-│ │ │ ├── User.swift
-│ │ │ └── Post.swift
-│ │ └── Repositories/
-│ │ ├── UserRepositoryProtocol.swift
-│ │ └── PostRepositoryProtocol.swift
-│ ├── Data/
-│ │ ├── Package.swift
-│ │ └── Sources/Data/
-│ │ ├── Repositories/
-│ │ │ ├── UserRepository.swift
-│ │ │ └── PostRepository.swift
-│ │ ├── Networking/
-│ │ │ ├── APIClient.swift
-│ │ │ └── DTOs/
-│ │ └── Persistence/
-│ │ └── CoreDataStack.swift
-│ ├── FeatureUserList/
-│ │ ├── Package.swift
-│ │ └── Sources/FeatureUserList/
-│ │ ├── Views/
-│ │ │ ├── UserListView.swift
-│ │ │ └── UserRowView.swift
-│ │ └── ViewModels/
-│ │ └── UserListViewModel.swift
-│ ├── FeatureUserDetail/
-│ │ ├── Package.swift
-│ │ └── Sources/FeatureUserDetail/
-│ │ └── ...
-│ ├── FeatureSettings/
-│ │ ├── Package.swift
-│ │ └── Sources/FeatureSettings/
-│ │ └── ...
-│ ├── CoreUI/
-│ │ ├── Package.swift
-│ │ └── Sources/CoreUI/
-│ │ ├── Components/
-│ │ │ ├── LoadingView.swift
-│ │ │ └── ErrorView.swift
-│ │ ├── Extensions/
-│ │ └── Theme/
-│ └── CoreUtilities/
-│ ├── Package.swift
-│ └── Sources/CoreUtilities/
-│ ├── Extensions/
-│ └── Logging/
-└── Tests/
- ├── DomainTests/
- ├── DataTests/
- ├── FeatureUserListTests/
- └── ...
-```
-
-**Package.swift example (FeatureUserList):**
-```swift
-// swift-tools-version: 5.9
-import PackageDescription
-
-let package = Package(
- name: "FeatureUserList",
- platforms: [.iOS(.v17), .macOS(.v14)],
- products: [
- .library(
- name: "FeatureUserList",
- targets: ["FeatureUserList"]
- ),
- ],
- dependencies: [
- .package(path: "../Domain"),
- .package(path: "../CoreUI"),
- ],
- targets: [
- .target(
- name: "FeatureUserList",
- dependencies: [
- "Domain",
- "CoreUI",
- ]
- ),
- .testTarget(
- name: "FeatureUserListTests",
- dependencies: ["FeatureUserList"]
- ),
- ]
-)
-```
-
-**Dependency hierarchy (bottom to top):**
-```
-App (top level - depends on everything)
-├── Feature modules (depend on Domain, CoreUI, CoreUtilities)
-├── Data (depends on Domain)
-├── Domain (no dependencies - pure Swift)
-├── CoreUI (depends on CoreUtilities)
-└── CoreUtilities (no dependencies - pure Swift)
-```
-
-**Key principles:**
-- Features are isolated SPM packages
-- Each package can be opened and worked on independently
-- Faster builds (only changed packages rebuild)
-- Domain layer has no framework dependencies (pure Swift)
-- Data layer implements Domain protocols
-- Features depend only on Domain (not on each other)
-- App layer composes everything
-
-**Benefits:**
-- Teams work on separate packages with minimal conflicts
-- Removing features = delete package folder
-- Faster SwiftUI previews (don't build unrelated code)
-- Enforced architectural boundaries via dependencies
-- Testable in isolation
-
-**When to modularize:**
-- More than 50 screens
-- More than 5 developers
-- Multiple platforms (iOS, macOS, watchOS)
-- When build times exceed 2-3 minutes
-
-
-## Sources
-
-- [Hacking with Swift: MVVM in SwiftUI](https://www.hackingwithswift.com/books/ios-swiftui/introducing-mvvm-into-your-swiftui-project)
-- [Medium: Modern MVVM in SwiftUI 2025](https://medium.com/@minalkewat/modern-mvvm-in-swiftui-2025-the-clean-architecture-youve-been-waiting-for-72a7d576648e)
-- [SwiftLee: MVVM Architectural Pattern](https://www.avanderlee.com/swiftui/mvvm-architectural-coding-pattern-to-structure-views/)
-- [Medium: SwiftUI in 2025 - Forget MVVM](https://dimillian.medium.com/swiftui-in-2025-forget-mvvm-262ff2bbd2ed)
-- [Alexey Naumov: Clean Architecture for SwiftUI](https://nalexn.github.io/clean-architecture-swiftui/)
-- [Medium: 2025's Best SwiftUI Architecture](https://medium.com/@minalkewat/2025s-best-swiftui-architecture-mvvm-clean-feature-modules-3a369a22858c)
-- [GitHub: Clean Architecture SwiftUI](https://github.com/nalexn/clean-architecture-swiftui)
-- [Medium: MVVM with Organized Folder Structures](https://medium.com/@rogeriocpires_128/implementing-mvvm-in-swiftui-with-organized-folder-structures-bc86845eead8)
-- [mokacoding: Dependency Injection in SwiftUI](https://mokacoding.com/blog/swiftui-dependency-injection/)
-- [Lucas van Dongen: Managing Dependencies in SwiftUI](https://lucasvandongen.dev/dependency_injection_swift_swiftui.php)
-- [GitHub: Factory - Swift Dependency Injection](https://github.com/hmlongco/Factory)
-- [Jesse Squires: @Observable Macro Deep Dive](https://www.jessesquires.com/blog/2024/09/09/swift-observable-macro/)
-- [SwiftLee: @Observable Performance](https://www.avanderlee.com/swiftui/observable-macro-performance-increase-observableobject/)
-- [Apple: Migrating to @Observable](https://developer.apple.com/documentation/swiftui/migrating-from-the-observable-object-protocol-to-the-observable-macro)
-- [Donny Wals: @Observable Explained](https://www.donnywals.com/observable-in-swiftui-explained/)
-- [Nimble: Modularizing iOS Apps with SwiftUI and SPM](https://nimblehq.co/blog/modern-approach-modularize-ios-swiftui-spm)
-- [Medium: Building Large-Scale Apps with SwiftUI](https://azamsharp.medium.com/building-large-scale-apps-with-swiftui-a-guide-to-modular-architecture-9c967be13001)
-- [Medium: Modular SwiftUI Architecture](https://medium.com/@pavel-holec/swiftui-modular-architecture-9bb1647b70b8)
-- [Better Programming: Factory Dependency Injection](https://betterprogramming.pub/factory-swift-dependency-injection-14da9b2b5d09)
-- [Lucas van Dongen: DI Frameworks Compared](https://lucasvandongen.dev/di_frameworks_compared.php)
-- [Medium: App vs Scene Protocol](https://medium.com/@ksjadhav2699/swiftui-app-vs-scene-protocol-1022e655a1fc)
-- [Swift with Majid: Managing App in SwiftUI](https://swiftwithmajid.com/2020/08/19/managing-app-in-swiftui/)
-- [GitHub: Swift Composable Architecture](https://github.com/pointfreeco/swift-composable-architecture)
-- [InfoQ: Swift Composable Architecture](https://www.infoq.com/news/2024/08/swift-composable-architecture/)
-- [Rod Schmidt: TCA 3 Year Experience](https://rodschmidt.com/posts/composable-architecture-experience/)
diff --git a/src/resources/skills/swiftui/references/layout-system.md b/src/resources/skills/swiftui/references/layout-system.md
deleted file mode 100644
index 369f19b24..000000000
--- a/src/resources/skills/swiftui/references/layout-system.md
+++ /dev/null
@@ -1,1186 +0,0 @@
-
-SwiftUI's layout system operates fundamentally differently from UIKit/Auto Layout. Instead of constraints, SwiftUI uses a **propose-measure-place** model:
-
-1. **Propose**: Parent offers child a size
-2. **Measure**: Child chooses its own size (parent must respect this)
-3. **Place**: Parent positions child in its coordinate space
-
-This creates a declarative, predictable layout system where conflicts are impossible. SwiftUI always produces a valid layout.
-
-**Read this file when:**
-- Choosing between layout containers (HStack, VStack, Grid, etc.)
-- Dealing with complex positioning requirements
-- Performance tuning layouts with large datasets
-- Understanding GeometryReader usage and alternatives
-
-**See also:**
-- `performance.md` for layout performance optimization strategies
-- `architecture.md` for structuring complex view hierarchies
-
-
-
-## Layout Containers
-
-
-**Purpose:** Horizontal arrangement of views from left to right (or right to left in RTL languages)
-
-**Behavior:** Proposes equal width to all children, then distributes remaining space based on flexibility. Children choose their own heights.
-
-**Alignment:** Default is `.center` vertically. Options: `.top`, `.center`, `.bottom`, `.firstTextBaseline`, `.lastTextBaseline`
-
-**Spacing:** Default is system-defined (typically 8pt). Override with `spacing:` parameter or `.none` for zero spacing.
-
-```swift
-// Common usage with custom spacing and alignment
-HStack(alignment: .top, spacing: 12) {
- Image(systemName: "person.circle")
- .font(.largeTitle)
-
- VStack(alignment: .leading, spacing: 4) {
- Text("Username")
- .font(.headline)
- Text("Online")
- .font(.caption)
- .foregroundStyle(.secondary)
- }
-
- Spacer() // Pushes content to leading edge
-
- Button("Follow") { }
-}
-.padding()
-```
-
-**Performance:** Lightweight. All children are created immediately (not lazy).
-
-
-
-**Purpose:** Vertical arrangement of views from top to bottom
-
-**Behavior:** Proposes equal height to all children, then distributes remaining space. Children choose their own widths.
-
-**Alignment:** Default is `.center` horizontally. Options: `.leading`, `.center`, `.trailing`
-
-**Spacing:** Default is system-defined. Override with `spacing:` parameter.
-
-```swift
-// Card layout with multiple sections
-VStack(alignment: .leading, spacing: 16) {
- Text("Title")
- .font(.headline)
-
- Text("Body text that can span multiple lines and will wrap naturally within the available width.")
- .font(.body)
- .foregroundStyle(.secondary)
-
- HStack {
- Spacer()
- Button("Action") { }
- }
-}
-.padding()
-.background(.background.secondary)
-.clipShape(RoundedRectangle(cornerRadius: 12))
-```
-
-**Performance:** Lightweight. All children are created immediately.
-
-
-
-**Purpose:** Layering views on the Z-axis (depth), drawing from back to front
-
-**Behavior:** Proposes full available size to all children. Final size is the union of all child sizes. Later views draw on top of earlier views.
-
-**Alignment:** Default is `.center` both horizontally and vertically. Options include `.topLeading`, `.bottomTrailing`, etc.
-
-```swift
-// Profile picture with badge
-ZStack(alignment: .bottomTrailing) {
- AsyncImage(url: profileURL) { image in
- image
- .resizable()
- .scaledToFill()
- } placeholder: {
- Color.gray
- }
- .frame(width: 100, height: 100)
- .clipShape(Circle())
-
- // Notification badge
- Circle()
- .fill(.red)
- .frame(width: 24, height: 24)
- .overlay {
- Text("3")
- .font(.caption2.bold())
- .foregroundStyle(.white)
- }
- .offset(x: 4, y: 4)
-}
-```
-
-**Performance:** Lightweight. Avoid excessive layering for complex effects (use `.overlay()` or `.background()` instead when appropriate).
-
-
-
-**Purpose:** Vertical stack with deferred view creation - only creates views when they scroll into view
-
-**When to use:**
-- Lists with hundreds or thousands of items
-- Items with expensive initialization (images, complex views)
-- Memory-constrained scenarios
-
-**Difference from VStack:**
-- VStack creates all children immediately
-- LazyVStack creates children on-demand as they appear
-- LazyVStack requires a ScrollView parent
-- LazyVStack calculates layout incrementally
-
-```swift
-ScrollView {
- LazyVStack(spacing: 12, pinnedViews: [.sectionHeaders]) {
- Section {
- ForEach(items) { item in
- ItemRow(item: item)
- .frame(height: 60)
- }
- } header: {
- Text("Section Header")
- .font(.headline)
- .padding()
- .frame(maxWidth: .infinity, alignment: .leading)
- .background(.ultraThinMaterial)
- }
- }
-}
-```
-
-**Performance:**
-- Superior for long lists (only renders visible views)
-- Slight overhead per view creation
-- Use `.id()` modifier to control view identity/reuse
-
-**See also:** LazyHStack for horizontal lazy loading
-
-
-
-**Purpose:** Grid layouts with lazy loading - creates cells only when visible
-
-**GridItem types:**
-- `.fixed(width)`: Exactly the specified width
-- `.flexible(minimum:maximum:)`: Grows to fill space within bounds
-- `.adaptive(minimum:maximum:)`: Creates as many columns as fit
-
-```swift
-// Photo grid with adaptive columns
-ScrollView {
- LazyVGrid(
- columns: [GridItem(.adaptive(minimum: 120, maximum: 200))],
- spacing: 12
- ) {
- ForEach(photos) { photo in
- AsyncImage(url: photo.url) { image in
- image
- .resizable()
- .scaledToFill()
- } placeholder: {
- Color.gray.opacity(0.3)
- }
- .frame(height: 120)
- .clipShape(RoundedRectangle(cornerRadius: 8))
- }
- }
- .padding()
-}
-
-// Fixed column layout (3 columns)
-let columns = [
- GridItem(.flexible()),
- GridItem(.flexible()),
- GridItem(.flexible())
-]
-
-LazyVGrid(columns: columns, spacing: 16) {
- ForEach(items) { item in
- ItemCard(item: item)
- }
-}
-```
-
-**Performance:**
-- Only creates visible cells (significant memory savings)
-- Best for image galleries, product catalogs, large datasets
-- On macOS, performance may drop below UIKit CollectionView for very large datasets (see performance.md)
-- Consider pagination for datasets over 1000 items
-
-**When NOT to use:** Small grids (< 20 items) - use Grid instead for simpler code
-
-
-
-**Purpose:** Non-lazy grid with explicit row/column control and advanced alignment
-
-**When to use:**
-- Small datasets where all items can be in memory
-- Need precise row/column control
-- Need GridRow for custom row styling
-- Need alignment across cells in different rows
-
-**Difference from LazyVGrid:**
-- Grid creates all cells immediately
-- Grid gives more layout control (GridRow, cell spanning)
-- Grid supports baseline alignment across rows
-- Grid is simpler for static content
-
-```swift
-// Form-like layout with alignment
-Grid(alignment: .leading, horizontalSpacing: 12, verticalSpacing: 8) {
- GridRow {
- Text("Name:")
- .gridColumnAlignment(.trailing)
- TextField("Enter name", text: $name)
- }
-
- GridRow {
- Text("Email:")
- .gridColumnAlignment(.trailing)
- TextField("Enter email", text: $email)
- }
-
- GridRow {
- Color.clear
- .gridCellUnsizedAxes(.horizontal)
-
- Button("Submit") { }
- .gridCellColumns(1)
- }
-}
-.padding()
-
-// Spanning cells
-Grid {
- GridRow {
- Text("Header")
- .gridCellColumns(3) // Spans 3 columns
- .font(.headline)
- }
-
- GridRow {
- ForEach(1...3, id: \.self) { num in
- Text("Cell \(num)")
- }
- }
-}
-```
-
-**Performance:** All cells created immediately. Keep under 100 items or use LazyVGrid.
-
-
-
-
-## GeometryReader
-
-**Purpose:** Access parent's proposed size and safe area insets for custom layout calculations
-
-**When to use:**
-- Custom drawing or graphics that need exact dimensions
-- Complex animations requiring precise positioning
-- Creating custom layout effects not possible with standard containers
-- Reading coordinate spaces for gesture calculations
-
-**When NOT to use:**
-- Simple relative sizing → use `.frame(maxWidth: .infinity)` or `Spacer()`
-- Container-relative frames → use `containerRelativeFrame()` (iOS 17+)
-- Adaptive layouts → use `ViewThatFits` (iOS 16+)
-- Safe area queries → use safe area modifiers directly
-
-```swift
-// Correct usage: Custom circular progress
-GeometryReader { geometry in
- ZStack {
- Circle()
- .stroke(Color.gray.opacity(0.3), lineWidth: 10)
-
- Circle()
- .trim(from: 0, to: progress)
- .stroke(Color.blue, style: StrokeStyle(lineWidth: 10, lineCap: .round))
- .rotationEffect(.degrees(-90))
- .animation(.easeInOut, value: progress)
- }
- .frame(width: geometry.size.width, height: geometry.size.width)
- .frame(maxWidth: .infinity, maxHeight: .infinity)
-}
-.aspectRatio(1, contentMode: .fit)
-
-// Correct usage: Reading coordinate spaces
-struct DragView: View {
- @State private var location: CGPoint = .zero
-
- var body: some View {
- GeometryReader { geometry in
- Circle()
- .fill(.blue)
- .frame(width: 50, height: 50)
- .position(location)
- .gesture(
- DragGesture(coordinateSpace: .named("container"))
- .onChanged { value in
- location = value.location
- }
- )
- }
- .coordinateSpace(name: "container")
- }
-}
-```
-
-**Pitfalls:**
-
-1. **Expands to fill all available space**: GeometryReader acts like `Color.clear.frame(maxWidth: .infinity, maxHeight: .infinity)`, which breaks layouts in ScrollViews and can cause infinite height calculations
-
-2. **Breaks ScrollView behavior**: Inside ScrollView, GeometryReader tries to fill infinite space, causing layout loops and broken scrolling
-
-3. **Overused for simple tasks**: Most GeometryReader usage can be replaced with simpler solutions
-
-4. **Deep nesting causes unpredictable behavior**: Nested GeometryReaders compound layout issues
-
-5. **Performance overhead**: Rebuilds view on every geometry change
-
-**Alternatives to GeometryReader:**
-
-```swift
-// ❌ BAD: Using GeometryReader for container-relative sizing
-GeometryReader { geometry in
- Rectangle()
- .frame(width: geometry.size.width * 0.5)
-}
-
-// ✅ GOOD: Use containerRelativeFrame (iOS 17+)
-Rectangle()
- .containerRelativeFrame(.horizontal) { width, _ in
- width * 0.5
- }
-
-// ✅ GOOD: Use frame modifiers
-Rectangle()
- .frame(maxWidth: .infinity)
- .padding(.horizontal, .infinity) // Creates 50% width
-
-// ❌ BAD: GeometryReader for adaptive layouts
-GeometryReader { geometry in
- if geometry.size.width > 600 {
- HStack { content }
- } else {
- VStack { content }
- }
-}
-
-// ✅ GOOD: Use ViewThatFits (iOS 16+)
-ViewThatFits {
- HStack { content }
- VStack { content }
-}
-```
-
-**Using GeometryReader safely:**
-
-```swift
-// Use in .background() or .overlay() to avoid affecting layout
-Text("Hello")
- .background(
- GeometryReader { geometry in
- Color.clear
- .onAppear {
- print("Size: \(geometry.size)")
- }
- }
- )
-```
-
-**See also:** `performance.md` for GeometryReader performance considerations
-
-
-
-## Custom Layout Protocol (iOS 16+)
-
-**When to use:**
-- Standard containers cannot achieve the desired layout
-- Flow/tag layouts (wrapping items like a text paragraph)
-- Radial/circular arrangements
-- Custom grid behaviors (masonry, Pinterest-style)
-- Complex alignment requirements across multiple views
-
-**Protocol requirements:**
-1. `sizeThatFits(proposal:subviews:cache:)`: Calculate and return container size
-2. `placeSubviews(in:proposal:subviews:cache:)`: Position each subview
-
-**Optional:**
-- `makeCache(subviews:)`: Create shared computation cache
-- `updateCache(_:subviews:)`: Update cache when subviews change
-- `explicitAlignment(of:in:proposal:subviews:cache:)`: Define custom alignment guides
-
-```swift
-// Complete FlowLayout example (tag cloud, wrapping items)
-struct FlowLayout: Layout {
- var spacing: CGFloat = 8
-
- func sizeThatFits(
- proposal: ProposedViewSize,
- subviews: Subviews,
- cache: inout Cache
- ) -> CGSize {
- let rows = computeRows(proposal: proposal, subviews: subviews)
-
- let width = proposal.replacingUnspecifiedDimensions().width
- let height = rows.reduce(0) { $0 + $1.height + spacing } - spacing
-
- return CGSize(width: width, height: height)
- }
-
- func placeSubviews(
- in bounds: CGRect,
- proposal: ProposedViewSize,
- subviews: Subviews,
- cache: inout Cache
- ) {
- let rows = computeRows(proposal: proposal, subviews: subviews)
-
- var y = bounds.minY
- for row in rows {
- var x = bounds.minX
-
- for index in row.subviewIndices {
- let subview = subviews[index]
- let size = subview.sizeThatFits(.unspecified)
-
- subview.place(
- at: CGPoint(x: x, y: y),
- proposal: ProposedViewSize(size)
- )
-
- x += size.width + spacing
- }
-
- y += row.height + spacing
- }
- }
-
- // Cache structure for performance
- struct Cache {
- var rows: [Row] = []
- }
-
- struct Row {
- var subviewIndices: [Int]
- var height: CGFloat
- }
-
- private func computeRows(
- proposal: ProposedViewSize,
- subviews: Subviews
- ) -> [Row] {
- let width = proposal.replacingUnspecifiedDimensions().width
- var rows: [Row] = []
- var currentRow: [Int] = []
- var currentX: CGFloat = 0
- var currentHeight: CGFloat = 0
-
- for (index, subview) in subviews.enumerated() {
- let size = subview.sizeThatFits(.unspecified)
-
- if currentX + size.width > width && !currentRow.isEmpty {
- // Start new row
- rows.append(Row(subviewIndices: currentRow, height: currentHeight))
- currentRow = []
- currentX = 0
- currentHeight = 0
- }
-
- currentRow.append(index)
- currentX += size.width + spacing
- currentHeight = max(currentHeight, size.height)
- }
-
- if !currentRow.isEmpty {
- rows.append(Row(subviewIndices: currentRow, height: currentHeight))
- }
-
- return rows
- }
-}
-
-// Usage
-FlowLayout(spacing: 12) {
- ForEach(tags, id: \.self) { tag in
- Text(tag)
- .padding(.horizontal, 12)
- .padding(.vertical, 6)
- .background(.blue.opacity(0.2))
- .clipShape(Capsule())
- }
-}
-
-// Radial layout example
-struct RadialLayout: Layout {
- func sizeThatFits(
- proposal: ProposedViewSize,
- subviews: Subviews,
- cache: inout ()
- ) -> CGSize {
- proposal.replacingUnspecifiedDimensions()
- }
-
- func placeSubviews(
- in bounds: CGRect,
- proposal: ProposedViewSize,
- subviews: Subviews,
- cache: inout ()
- ) {
- let radius = min(bounds.width, bounds.height) / 2.5
- let center = CGPoint(x: bounds.midX, y: bounds.midY)
- let angle = (2 * .pi) / Double(subviews.count)
-
- for (index, subview) in subviews.enumerated() {
- let theta = angle * Double(index) - .pi / 2
- let x = center.x + radius * cos(theta)
- let y = center.y + radius * sin(theta)
-
- subview.place(
- at: CGPoint(x: x, y: y),
- anchor: .center,
- proposal: .unspecified
- )
- }
- }
-}
-```
-
-**Use cases:**
-- **Flow/tag layout**: Wrapping items like tags, badges, or chips
-- **Radial layout**: Circular menu, dial controls
-- **Masonry grid**: Pinterest-style uneven grid
-- **Custom calendar**: Week views with variable heights
-- **Waterfall layout**: Staggered grid with varying item heights
-
-**See also:**
-- Apple's official documentation: [Composing custom layouts with SwiftUI](https://developer.apple.com/documentation/swiftui/composing_custom_layouts_with_swiftui)
-- `performance.md` for Layout protocol caching strategies
-
-
-
-## Alignment and Alignment Guides
-
-**Built-in alignments:**
-- **Vertical**: `.leading`, `.center`, `.trailing`
-- **Horizontal**: `.top`, `.center`, `.bottom`, `.firstTextBaseline`, `.lastTextBaseline`
-
-**How alignment works:** When containers like HStack or VStack align children, they use alignment guides. Each view exposes alignment guide values, and the container aligns those values across children.
-
-**Custom alignment guides:**
-
-```swift
-// Define custom vertical alignment
-extension VerticalAlignment {
- private struct MidAccountAndName: AlignmentID {
- static func defaultValue(in context: ViewDimensions) -> CGFloat {
- context[VerticalAlignment.center]
- }
- }
-
- static let midAccountAndName = VerticalAlignment(MidAccountAndName.self)
-}
-
-// Use custom alignment
-HStack(alignment: .midAccountAndName) {
- VStack(alignment: .trailing) {
- Text("Full Name:")
- Text("Address:")
- Text("Account Number:")
- .alignmentGuide(.midAccountAndName) { d in
- d[VerticalAlignment.center]
- }
- }
-
- VStack(alignment: .leading) {
- Text("John Doe")
- Text("123 Main St")
- Text("98765-4321")
- .alignmentGuide(.midAccountAndName) { d in
- d[VerticalAlignment.center]
- }
- }
-}
-
-// Adjusting alignment dynamically
-Image(systemName: "arrow.up")
- .alignmentGuide(.leading) { d in
- d[.leading] - 50 // Shift left by 50 points
- }
-```
-
-**Preference keys for custom alignment:**
-
-Preference keys allow child views to communicate layout information up the hierarchy.
-
-```swift
-// Define preference key for collecting bounds
-struct BoundsPreferenceKey: PreferenceKey {
- static var defaultValue: [String: Anchor] = [:]
-
- static func reduce(
- value: inout [String: Anchor],
- nextValue: () -> [String: Anchor
-
-
-## Safe Area Handling
-
-SwiftUI respects safe areas by default (avoiding notches, home indicator, status bar).
-
-**Three key modifiers:**
-
-### `ignoresSafeArea(_:edges:)`
-**When to use:** Extend backgrounds or images edge-to-edge while keeping content safe
-
-```swift
-// Background that extends to edges
-ZStack {
- Color.blue
- .ignoresSafeArea() // Goes edge-to-edge
-
- VStack {
- Text("Content")
- Spacer()
- }
- .padding() // Content stays in safe area
-}
-
-// Ignore only specific edges
-ScrollView {
- content
-}
-.ignoresSafeArea(.container, edges: .bottom)
-
-// Ignore keyboard safe area
-TextField("Message", text: $message)
- .ignoresSafeArea(.keyboard)
-```
-
-**Regions:**
-- `.container`: Device edges, status bar, home indicator
-- `.keyboard`: Soft keyboard area
-- `.all`: Both container and keyboard
-
-**Edges:** `.top`, `.bottom`, `.leading`, `.trailing`, `.horizontal`, `.vertical`, `.all`
-
-### `safeAreaInset(edge:alignment:spacing:content:)`
-**When to use:** Add custom bars (toolbars, tab bars) that shrink the safe area for other content
-
-```swift
-// Custom bottom toolbar
-ScrollView {
- ForEach(items) { item in
- ItemRow(item: item)
- }
-}
-.safeAreaInset(edge: .bottom, spacing: 0) {
- HStack {
- Button("Action 1") { }
- Spacer()
- Button("Action 2") { }
- }
- .padding()
- .background(.ultraThinMaterial)
-}
-
-// Multiple insets stack
-ScrollView {
- content
-}
-.safeAreaInset(edge: .top) {
- SearchBar()
-}
-.safeAreaInset(edge: .bottom) {
- BottomBar()
-}
-
-// Inset with custom alignment
-List(messages) { message in
- MessageRow(message: message)
-}
-.safeAreaInset(edge: .bottom, alignment: .trailing) {
- Button(action: compose) {
- Image(systemName: "plus.circle.fill")
- .font(.largeTitle)
- }
- .padding()
-}
-```
-
-**Key behavior:** Unlike `ignoresSafeArea`, this **shrinks** the safe area so other views avoid it.
-
-### `safeAreaPadding(_:_:)` (iOS 17+)
-**When to use:** Extend safe area by a fixed amount without providing a view
-
-```swift
-// Add padding to safe area
-ScrollView {
- content
-}
-.safeAreaPadding(.horizontal, 20)
-.safeAreaPadding(.bottom, 60)
-
-// Equivalent to safeAreaInset but cleaner when you don't need a view
-```
-
-**Difference from `.padding()`:**
-- `.padding()` adds space but doesn't affect safe area calculations
-- `.safeAreaPadding()` extends the safe area itself
-
-### Accessing safe area values
-
-```swift
-// Read safe area insets
-GeometryReader { geometry in
- let safeArea = geometry.safeAreaInsets
-
- VStack {
- Text("Top: \(safeArea.top)")
- Text("Bottom: \(safeArea.bottom)")
- }
-}
-```
-
-**See also:** [Managing safe area in SwiftUI](https://swiftwithmajid.com/2021/11/03/managing-safe-area-in-swiftui/)
-
-
-
-## Choosing the Right Layout
-
-**Simple horizontal arrangement:**
-- Use `HStack` with alignment and spacing parameters
-- Use `Spacer()` to push content to edges
-
-**Simple vertical arrangement:**
-- Use `VStack` with alignment and spacing parameters
-- Consider `LazyVStack` if list exceeds 50+ items
-
-**Overlapping views:**
-- Use `ZStack` for basic layering
-- Use `.overlay()` or `.background()` for single overlay/underlay
-- Consider Custom Layout for complex Z-ordering logic
-
-**Long scrolling list:**
-- Use `LazyVStack` inside `ScrollView` for variable content
-- Use `List` for standard iOS list appearance with built-in features (swipe actions, separators)
-- Vertical scrolling: `ScrollView { LazyVStack { } }`
-- Horizontal scrolling: `ScrollView(.horizontal) { LazyHStack { } }`
-
-**Grid of items:**
-- **Small grid (< 20 items):** Use `Grid` for full control
-- **Large grid:** Use `LazyVGrid` or `LazyHGrid`
-- **Fixed columns:** `LazyVGrid(columns: [GridItem(.flexible()), ...])`
-- **Adaptive columns:** `LazyVGrid(columns: [GridItem(.adaptive(minimum: 120))])`
-
-**Need parent size:**
-- **iOS 17+:** Use `containerRelativeFrame()` for size relative to container
-- **iOS 16+:** Use `ViewThatFits` for adaptive layouts
-- **Custom drawing/gestures:** Use `GeometryReader` sparingly
-- **Simple fills:** Use `.frame(maxWidth: .infinity)`
-
-**Adaptive layout (changes based on space):**
-- Use `ViewThatFits` (iOS 16+) to switch between layouts
-- Use size classes with `@Environment(\.horizontalSizeClass)`
-
-**Complex custom layout:**
-- Implement Custom Layout protocol (iOS 16+)
-- Use for: flow layouts, radial layouts, masonry grids
-- Provides full control over sizing and positioning
-
-**Performance considerations:**
-
-| Scenario | Recommendation | Reason |
-|----------|---------------|--------|
-| Static grid < 20 items | Grid | Simpler, all layout upfront |
-| Dynamic list 50+ items | LazyVStack | Only renders visible |
-| Photo gallery 100+ items | LazyVGrid | Memory efficient |
-| Constantly changing list | LazyVStack with `.id()` | Controls view identity |
-| macOS high FPS requirement | UIKit/AppKit wrapper | SwiftUI grids cap at ~90fps |
-| Complex nesting 5+ levels | Custom Layout | Better control, fewer containers |
-
-**See also:** `performance.md` for detailed performance tuning strategies
-
-
-
-## What NOT to Do
-
-
-**Problem:** Using GeometryReader when simpler solutions exist
-
-**Example:**
-```swift
-// ❌ Overcomplicated
-GeometryReader { geometry in
- Rectangle()
- .frame(width: geometry.size.width * 0.8)
-}
-
-// ✅ Simple and correct
-Rectangle()
- .frame(maxWidth: .infinity)
- .padding(.horizontal, 40) // Creates inset
-```
-
-**Why it's bad:**
-- GeometryReader expands to fill all space, breaking layouts
-- Causes performance overhead
-- Makes code harder to understand
-- Often causes issues in ScrollViews
-
-**Instead:**
-- Use `.frame(maxWidth: .infinity)` for full width
-- Use `containerRelativeFrame()` (iOS 17+) for proportional sizing
-- Use `ViewThatFits` (iOS 16+) for adaptive layouts
-- Reserve GeometryReader for actual coordinate-space needs
-
-
-
-**Problem:** Excessive nesting of HStack/VStack creating deep hierarchies
-
-**Example:**
-```swift
-// ❌ Too many nested stacks
-VStack {
- HStack {
- VStack {
- HStack {
- VStack {
- Text("Title")
- Text("Subtitle")
- }
- }
- }
- }
-}
-
-// ✅ Flattened with proper modifiers
-VStack(alignment: .leading, spacing: 4) {
- Text("Title")
- .font(.headline)
- Text("Subtitle")
- .font(.subheadline)
- .foregroundStyle(.secondary)
-}
-```
-
-**Why it's bad:**
-- Harder to read and maintain
-- Unnecessary view hierarchy depth
-- Can impact performance with many views
-- Makes alignment more complex
-
-**Instead:**
-- Use alignment and spacing parameters instead of wrapper stacks
-- Extract complex views into separate components
-- Use Grid for form-like layouts
-- Consider Custom Layout for truly complex arrangements
-
-
-
-**Problem:** Using LazyVStack outside a ScrollView
-
-**Example:**
-```swift
-// ❌ LazyVStack needs a scrollable container
-LazyVStack {
- ForEach(items) { item in
- Text(item.name)
- }
-}
-
-// ✅ Correct usage
-ScrollView {
- LazyVStack {
- ForEach(items) { item in
- Text(item.name)
- }
- }
-}
-
-// ✅ Or just use VStack if not scrolling
-VStack {
- ForEach(items) { item in
- Text(item.name)
- }
-}
-```
-
-**Why it's bad:**
-- LazyVStack requires a scrollable parent to know when to load views
-- Without scrolling, there's no benefit to lazy loading
-- Can cause unexpected layout behavior
-
-**Instead:**
-- Always wrap LazyVStack/LazyHStack in ScrollView
-- If not scrolling, use regular VStack/HStack
-
-
-
-**Problem:** Using `.fixed()` GridItem when flexible sizing would work better
-
-**Example:**
-```swift
-// ❌ Fixed sizes break on different screen sizes
-LazyVGrid(columns: [
- GridItem(.fixed(150)),
- GridItem(.fixed(150))
-]) {
- ForEach(items) { item in
- ItemView(item: item)
- }
-}
-
-// ✅ Adaptive sizing
-LazyVGrid(columns: [
- GridItem(.adaptive(minimum: 150, maximum: 200))
-]) {
- ForEach(items) { item in
- ItemView(item: item)
- }
-}
-
-// ✅ Flexible columns
-LazyVGrid(columns: [
- GridItem(.flexible()),
- GridItem(.flexible())
-]) {
- ForEach(items) { item in
- ItemView(item: item)
- }
-}
-```
-
-**Why it's bad:**
-- Doesn't adapt to different screen sizes (iPhone SE vs iPad)
-- Creates horizontal scrolling or cut-off content
-- Not responsive to orientation changes
-
-**Instead:**
-- Use `.flexible()` to let items share space proportionally
-- Use `.adaptive()` to fit as many items as possible
-- Reserve `.fixed()` for specific design requirements (icons, avatars)
-
-
-
-**Problem:** Using multiple Spacers when alignment parameters would be clearer
-
-**Example:**
-```swift
-// ❌ Confusing spacer usage
-HStack {
- Spacer()
- Text("Centered?")
- Spacer()
- Spacer()
-}
-
-// ✅ Clear alignment
-HStack {
- Spacer()
- Text("Centered")
- Spacer()
-}
-
-// ✅ Even better - use alignment
-HStack {
- Text("Centered")
-}
-.frame(maxWidth: .infinity)
-
-// ✅ For trailing alignment
-HStack {
- Spacer()
- Text("Trailing")
-}
-```
-
-**Why it's bad:**
-- Multiple spacers create ambiguous spacing
-- Harder to reason about layout
-- Can cause unexpected behavior with different content sizes
-
-**Instead:**
-- Use single Spacer() for clear intent
-- Use frame modifiers with alignment
-- Use stack alignment parameters
-
-
-
-**Problem:** Using LazyVStack for small lists or VStack for huge lists
-
-**Example:**
-```swift
-// ❌ Lazy overhead for tiny list
-ScrollView {
- LazyVStack {
- ForEach(0..<5) { i in
- Text("Item \(i)")
- }
- }
-}
-
-// ✅ Just use VStack
-ScrollView {
- VStack {
- ForEach(0..<5) { i in
- Text("Item \(i)")
- }
- }
-}
-
-// ❌ Regular stack for huge list
-VStack {
- ForEach(0..<1000) { i in
- ExpensiveView(index: i)
- }
-}
-
-// ✅ Lazy for performance
-ScrollView {
- LazyVStack {
- ForEach(0..<1000) { i in
- ExpensiveView(index: i)
- }
- }
-}
-```
-
-**Why it's bad:**
-- Lazy containers add overhead for small datasets
-- Non-lazy containers create all views upfront (memory/performance hit)
-
-**Instead:**
-- **< 20 simple items:** Use VStack/HStack
-- **20-50 items:** Test both; likely VStack is fine
-- **> 50 items or complex views:** Use LazyVStack/LazyHStack
-- **Large images/media:** Always use lazy
-
-
-
-**Problem:** Providing fixed frames to ViewThatFits children, defeating its purpose
-
-**Example:**
-```swift
-// ❌ Fixed frames prevent ViewThatFits from working
-ViewThatFits {
- HStack {
- content
- }
- .frame(width: 600) // Prevents fitting logic
-
- VStack {
- content
- }
- .frame(width: 300)
-}
-
-// ✅ Let views size naturally
-ViewThatFits {
- HStack {
- content
- }
-
- VStack {
- content
- }
-}
-```
-
-**Why it's bad:**
-- ViewThatFits needs to measure ideal sizes to choose the right view
-- Fixed frames override this measurement
-- Defeats the entire purpose of adaptive layout
-
-**Instead:**
-- Let child views size themselves naturally
-- Use maxWidth/maxHeight if needed, not fixed sizes
-- Trust ViewThatFits to pick the right layout
-
-
-
-
-**Sources:**
-Research for this reference included:
-- [SwiftUI Layout System (kean.blog)](https://kean.blog/post/swiftui-layout-system)
-- [Custom Layouts in SwiftUI (Medium)](https://medium.com/@wesleymatlock/custom-layouts-in-swiftui-a-deep-dive-into-the-layout-protocol-5edc691cd4fb)
-- [A guide to the SwiftUI layout system (Swift by Sundell)](https://www.swiftbysundell.com/articles/swiftui-layout-system-guide-part-1/)
-- [Creating custom layouts with Layout protocol (Hacking with Swift)](https://www.hackingwithswift.com/quick-start/swiftui/how-to-create-a-custom-layout-using-the-layout-protocol)
-- [Apple Developer: Composing custom layouts with SwiftUI](https://developer.apple.com/documentation/swiftui/composing_custom_layouts_with_swiftui)
-- [Custom Layout in SwiftUI (Sarunw)](https://sarunw.com/posts/swiftui-custom-layout/)
-- [GeometryReader - Blessing or Curse? (fatbobman)](https://fatbobman.com/en/posts/geometryreader-blessing-or-curse/)
-- [Mastering GeometryReader in SwiftUI (DEV Community)](https://dev.to/qmshahzad/mastering-geometryreader-in-swiftui-from-basics-to-advanced-layout-control-5akk)
-- [SwiftUI Grid, LazyVGrid, LazyHGrid (avanderlee)](https://www.avanderlee.com/swiftui/grid-lazyvgrid-lazyhgrid-gridviews/)
-- [Tuning Lazy Stacks and Grids Performance Guide (Medium)](https://medium.com/@wesleymatlock/tuning-lazy-stacks-and-grids-in-swiftui-a-performance-guide-2fb10786f76a)
-- [containerRelativeFrame Modifier (fatbobman)](https://fatbobman.com/en/posts/mastering-the-containerrelativeframe-modifier-in-swiftui/)
-- [ViewThatFits adaptive layout (Hacking with Swift)](https://www.hackingwithswift.com/quick-start/swiftui/how-to-create-an-adaptive-layout-with-viewthatfits)
-- [Mastering ViewThatFits (fatbobman)](https://fatbobman.com/en/posts/mastering-viewthatfits/)
-- [Alignment Guides in SwiftUI (The SwiftUI Lab)](https://swiftui-lab.com/alignment-guides/)
-- [Managing safe area in SwiftUI (Swift with Majid)](https://swiftwithmajid.com/2021/11/03/managing-safe-area-in-swiftui/)
-- [Mastering Safe Area in SwiftUI (fatbobman)](https://fatbobman.com/en/posts/safearea/)
diff --git a/src/resources/skills/swiftui/references/navigation.md b/src/resources/skills/swiftui/references/navigation.md
deleted file mode 100644
index 8bb98b0ac..000000000
--- a/src/resources/skills/swiftui/references/navigation.md
+++ /dev/null
@@ -1,1492 +0,0 @@
-
-SwiftUI navigation has evolved significantly with NavigationStack (iOS 16+) replacing the deprecated NavigationView. The modern navigation model provides type-safe, programmatic control while supporting both user-driven and code-driven navigation patterns.
-
-**Key insight:** NavigationStack with NavigationPath provides a stack-based navigation system where you can programmatically manipulate the navigation hierarchy while SwiftUI keeps the UI in sync automatically.
-
-**Read this file when:** Building multi-screen apps, implementing deep linking, managing programmatic navigation, presenting sheets and modals, or setting up tab-based navigation.
-
-**Related files:**
-- architecture.md - Coordinator pattern for complex navigation flows
-- state-management.md - Managing navigation state with @Observable
-- platform-integration.md - Platform-specific navigation differences (iOS vs macOS)
-
-
-
-## NavigationStack
-
-NavigationStack manages a stack of views with type-safe routing. It replaces the deprecated NavigationView and provides better programmatic control.
-
-**Basic usage with NavigationLink:**
-```swift
-struct ContentView: View {
- var body: some View {
- NavigationStack {
- List {
- NavigationLink("Details", value: "details")
- NavigationLink("Settings", value: "settings")
- }
- .navigationTitle("Home")
- .navigationDestination(for: String.self) { value in
- Text("Showing: \(value)")
- }
- }
- }
-}
-```
-
-**With NavigationPath for programmatic navigation:**
-```swift
-struct ContentView: View {
- @State private var path = NavigationPath()
-
- var body: some View {
- NavigationStack(path: $path) {
- VStack {
- Button("Go to Details") {
- path.append("details")
- }
-
- Button("Go Deep (3 levels)") {
- path.append("level1")
- path.append("level2")
- path.append("level3")
- }
- }
- .navigationTitle("Home")
- .navigationDestination(for: String.self) { value in
- DetailView(value: value, path: $path)
- }
- }
- }
-}
-
-struct DetailView: View {
- let value: String
- @Binding var path: NavigationPath
-
- var body: some View {
- VStack {
- Text("Showing: \(value)")
-
- Button("Pop to Root") {
- path = NavigationPath()
- }
- }
- }
-}
-```
-
-**navigationDestination modifier:**
-
-The navigationDestination modifier enables type-based routing. You can register multiple destination handlers for different data types:
-
-```swift
-struct MultiTypeNavigation: View {
- @State private var path = NavigationPath()
-
- var body: some View {
- NavigationStack(path: $path) {
- List {
- Button("Show User") {
- path.append(User(id: 1, name: "Alice"))
- }
-
- Button("Show Product") {
- path.append(Product(id: 100, title: "iPhone"))
- }
- }
- .navigationDestination(for: User.self) { user in
- UserDetailView(user: user)
- }
- .navigationDestination(for: Product.self) { product in
- ProductDetailView(product: product)
- }
- }
- }
-}
-
-struct User: Hashable, Codable {
- let id: Int
- let name: String
-}
-
-struct Product: Hashable, Codable {
- let id: Int
- let title: String
-}
-```
-
-**Key rules:**
-- Place navigationDestination inside NavigationStack, not on child views
-- Don't place navigationDestination on lazy containers (List, ScrollView, LazyVStack)
-- Top-level navigationDestination always overrides lower ones for the same type
-- Each destination type must be Hashable
-
-**Navigation state in @Observable:**
-```swift
-import Observation
-
-@Observable
-class NavigationManager {
- var path = NavigationPath()
-
- func push(_ destination: Destination) {
- path.append(destination)
- }
-
- func pop() {
- guard !path.isEmpty else { return }
- path.removeLast()
- }
-
- func popToRoot() {
- path = NavigationPath()
- }
-}
-
-enum Destination: Hashable {
- case detail(id: Int)
- case settings
- case profile
-}
-
-struct AppView: View {
- @State private var navigation = NavigationManager()
-
- var body: some View {
- @Bindable var nav = navigation
-
- NavigationStack(path: $nav.path) {
- List {
- Button("Details") {
- navigation.push(.detail(id: 1))
- }
-
- Button("Settings") {
- navigation.push(.settings)
- }
- }
- .navigationDestination(for: Destination.self) { destination in
- switch destination {
- case .detail(let id):
- DetailView(id: id, navigation: navigation)
- case .settings:
- SettingsView(navigation: navigation)
- case .profile:
- ProfileView(navigation: navigation)
- }
- }
- }
- }
-}
-```
-
-
-
-## Programmatic Navigation
-
-NavigationPath provides programmatic control over the navigation stack without requiring NavigationLink user interaction.
-
-**Push to path:**
-```swift
-// Push single destination
-path.append(DetailDestination.item(id: 123))
-
-// Push multiple levels at once
-path.append(contentsOf: [screen1, screen2, screen3])
-```
-
-**Pop operations:**
-```swift
-// Pop one level
-path.removeLast()
-
-// Pop multiple levels
-path.removeLast(2)
-
-// Pop to root (clear entire stack)
-path = NavigationPath()
-
-// Conditional pop
-if path.count > 0 {
- path.removeLast()
-}
-```
-
-**Deep navigation example:**
-```swift
-@Observable
-class Router {
- var path = NavigationPath()
-
- func navigateToUserPosts(userId: Int, postId: Int) {
- // Navigate through multiple screens
- path.append(Route.userDetail(userId))
- path.append(Route.userPosts(userId))
- path.append(Route.postDetail(postId))
- }
-
- func popToUserDetail() {
- // Remove specific number of levels
- if path.count >= 2 {
- path.removeLast(2)
- }
- }
-}
-
-enum Route: Hashable {
- case userDetail(Int)
- case userPosts(Int)
- case postDetail(Int)
-}
-```
-
-**NavigationPath count and inspection:**
-```swift
-struct NavigationDebugView: View {
- @State private var path = NavigationPath()
-
- var body: some View {
- NavigationStack(path: $path) {
- VStack {
- Text("Stack depth: \(path.count)")
-
- Button("Push") {
- path.append("Level \(path.count + 1)")
- }
-
- Button("Pop") {
- if !path.isEmpty {
- path.removeLast()
- }
- }
-
- Button("Pop to Root") {
- path = NavigationPath()
- }
- }
- .navigationDestination(for: String.self) { value in
- Text(value)
- }
- }
- }
-}
-```
-
-
-
-## Sheet and FullScreenCover
-
-Sheets present modal content on top of the current view. They are not part of the NavigationStack hierarchy.
-
-**Basic sheet with boolean:**
-```swift
-struct SheetExample: View {
- @State private var showingSheet = false
-
- var body: some View {
- Button("Show Sheet") {
- showingSheet = true
- }
- .sheet(isPresented: $showingSheet) {
- SheetContentView()
- }
- }
-}
-
-struct SheetContentView: View {
- @Environment(\.dismiss) private var dismiss
-
- var body: some View {
- NavigationStack {
- VStack {
- Text("Sheet Content")
- Button("Close") {
- dismiss()
- }
- }
- .navigationTitle("Modal")
- .toolbar {
- ToolbarItem(placement: .cancellationAction) {
- Button("Cancel") {
- dismiss()
- }
- }
- }
- }
- }
-}
-```
-
-**Item-based presentation (type-safe, recommended):**
-```swift
-struct ItemSheet: View {
- @State private var selectedUser: User?
-
- var body: some View {
- List(users) { user in
- Button(user.name) {
- selectedUser = user
- }
- }
- .sheet(item: $selectedUser) { user in
- UserDetailSheet(user: user)
- }
- }
-}
-
-struct User: Identifiable {
- let id: UUID
- let name: String
-}
-
-struct UserDetailSheet: View {
- let user: User
- @Environment(\.dismiss) private var dismiss
-
- var body: some View {
- NavigationStack {
- VStack {
- Text("User: \(user.name)")
- }
- .navigationTitle(user.name)
- .toolbar {
- ToolbarItem(placement: .confirmationAction) {
- Button("Done") {
- dismiss()
- }
- }
- }
- }
- }
-}
-```
-
-**FullScreenCover:**
-```swift
-struct FullScreenExample: View {
- @State private var showingFullScreen = false
-
- var body: some View {
- Button("Show Full Screen") {
- showingFullScreen = true
- }
- .fullScreenCover(isPresented: $showingFullScreen) {
- FullScreenContentView()
- }
- }
-}
-
-struct FullScreenContentView: View {
- @Environment(\.dismiss) private var dismiss
-
- var body: some View {
- NavigationStack {
- VStack {
- Text("Full Screen Content")
- .font(.largeTitle)
- }
- .toolbar {
- ToolbarItem(placement: .cancellationAction) {
- Button("Close") {
- dismiss()
- }
- }
- }
- }
- }
-}
-```
-
-**Presentation detents (iOS 16+):**
-```swift
-struct DetentSheet: View {
- @State private var showingSheet = false
- @State private var selectedDetent: PresentationDetent = .medium
-
- var body: some View {
- Button("Show Customizable Sheet") {
- showingSheet = true
- }
- .sheet(isPresented: $showingSheet) {
- SheetWithDetents(selectedDetent: $selectedDetent)
- .presentationDetents(
- [.medium, .large, .fraction(0.25), .height(200)],
- selection: $selectedDetent
- )
- .presentationDragIndicator(.visible)
- .presentationBackgroundInteraction(.enabled(upThrough: .medium))
- }
- }
-}
-
-struct SheetWithDetents: View {
- @Binding var selectedDetent: PresentationDetent
-
- var body: some View {
- VStack {
- Text("Drag to resize")
- .font(.headline)
-
- Text("Current detent: \(detentDescription)")
- .font(.caption)
- }
- .padding()
- }
-
- var detentDescription: String {
- if selectedDetent == .medium { return "Medium" }
- if selectedDetent == .large { return "Large" }
- return "Custom"
- }
-}
-```
-
-**Dismiss from presented view:**
-```swift
-struct DismissExample: View {
- @Environment(\.dismiss) private var dismiss
-
- var body: some View {
- VStack {
- Text("Modal Content")
-
- Button("Dismiss") {
- dismiss()
- }
- }
- }
-}
-```
-
-
-
-## TabView
-
-TabView presents multiple independent navigation hierarchies. Each tab typically contains its own NavigationStack.
-
-**Basic TabView:**
-```swift
-struct TabExample: View {
- var body: some View {
- TabView {
- HomeView()
- .tabItem {
- Label("Home", systemImage: "house")
- }
-
- SearchView()
- .tabItem {
- Label("Search", systemImage: "magnifyingglass")
- }
-
- ProfileView()
- .tabItem {
- Label("Profile", systemImage: "person")
- }
- }
- }
-}
-
-struct HomeView: View {
- var body: some View {
- NavigationStack {
- List {
- Text("Home Content")
- }
- .navigationTitle("Home")
- }
- }
-}
-```
-
-**Programmatic tab selection:**
-```swift
-struct ProgrammaticTabView: View {
- @State private var selectedTab = Tab.home
-
- var body: some View {
- TabView(selection: $selectedTab) {
- HomeView()
- .tabItem {
- Label("Home", systemImage: "house")
- }
- .tag(Tab.home)
-
- SearchView()
- .tabItem {
- Label("Search", systemImage: "magnifyingglass")
- }
- .tag(Tab.search)
-
- ProfileView()
- .tabItem {
- Label("Profile", systemImage: "person")
- }
- .tag(Tab.profile)
- }
- .onChange(of: selectedTab) { oldValue, newValue in
- print("Tab changed from \(oldValue) to \(newValue)")
- }
- }
-}
-
-enum Tab {
- case home
- case search
- case profile
-}
-```
-
-**Each tab with independent NavigationStack:**
-```swift
-struct IndependentTabStacks: View {
- @State private var homeNavPath = NavigationPath()
- @State private var searchNavPath = NavigationPath()
-
- var body: some View {
- TabView {
- NavigationStack(path: $homeNavPath) {
- HomeRootView()
- .navigationDestination(for: HomeDestination.self) { destination in
- // Home-specific destinations
- Text("Home destination")
- }
- }
- .tabItem {
- Label("Home", systemImage: "house")
- }
-
- NavigationStack(path: $searchNavPath) {
- SearchRootView()
- .navigationDestination(for: SearchDestination.self) { destination in
- // Search-specific destinations
- Text("Search destination")
- }
- }
- .tabItem {
- Label("Search", systemImage: "magnifyingglass")
- }
- }
- }
-}
-
-enum HomeDestination: Hashable {
- case detail(Int)
-}
-
-enum SearchDestination: Hashable {
- case results(String)
-}
-```
-
-**iOS 18 Tab API:**
-```swift
-// iOS 18 introduces new Tab syntax with better customization
-@available(iOS 18.0, *)
-struct ModernTabView: View {
- @State private var selectedTab: TabIdentifier = .home
-
- var body: some View {
- TabView(selection: $selectedTab) {
- Tab("Home", systemImage: "house", value: .home) {
- NavigationStack {
- HomeView()
- }
- }
-
- Tab("Search", systemImage: "magnifyingglass", value: .search) {
- NavigationStack {
- SearchView()
- }
- }
- .badge(5) // Badge support
-
- Tab("Profile", systemImage: "person", value: .profile) {
- NavigationStack {
- ProfileView()
- }
- }
- .customizationID("profile") // Enables tab customization
- }
- .tabViewStyle(.sidebarAdaptable) // Sidebar on iPad
- }
-}
-
-enum TabIdentifier: Hashable {
- case home
- case search
- case profile
-}
-```
-
-**Tab badges:**
-```swift
-struct BadgedTabs: View {
- @State private var unreadCount = 3
-
- var body: some View {
- TabView {
- HomeView()
- .tabItem {
- Label("Home", systemImage: "house")
- }
-
- MessagesView()
- .tabItem {
- Label("Messages", systemImage: "message")
- }
- .badge(unreadCount)
-
- ProfileView()
- .tabItem {
- Label("Profile", systemImage: "person")
- }
- }
- }
-}
-```
-
-**Platform differences:**
-- **iOS:** Bottom tabs with up to 5 visible items (more creates "More" tab)
-- **macOS:** Top tabs or sidebar style
-- **iPadOS:** Can transform to sidebar with .tabViewStyle(.sidebarAdaptable)
-- **watchOS:** PageTabViewStyle (swipeable pages)
-
-
-
-## Deep Linking
-
-Deep linking enables opening your app to specific screens via URLs, supporting both custom URL schemes and Universal Links.
-
-**URL handling with onOpenURL:**
-```swift
-@main
-struct MyApp: App {
- @State private var router = Router()
-
- var body: some Scene {
- WindowGroup {
- ContentView()
- .environment(router)
- .onOpenURL { url in
- handleDeepLink(url)
- }
- }
- }
-
- private func handleDeepLink(_ url: URL) {
- router.handleDeepLink(url)
- }
-}
-
-@Observable
-class Router {
- var path = NavigationPath()
-
- func handleDeepLink(_ url: URL) {
- // Parse URL and update navigation
- guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
- return
- }
-
- // myapp://user/123
- if components.scheme == "myapp",
- components.host == "user",
- let userId = components.path.split(separator: "/").first,
- let id = Int(userId) {
- navigateToUser(id: id)
- }
-
- // myapp://product/456/reviews
- if components.scheme == "myapp",
- components.host == "product" {
- let pathComponents = components.path.split(separator: "/")
- if let productId = pathComponents.first,
- let id = Int(productId) {
- navigateToProduct(id: id, showReviews: pathComponents.contains("reviews"))
- }
- }
- }
-
- func navigateToUser(id: Int) {
- path = NavigationPath() // Reset to root
- path.append(Route.userDetail(id))
- }
-
- func navigateToProduct(id: Int, showReviews: Bool) {
- path = NavigationPath()
- path.append(Route.productDetail(id))
- if showReviews {
- path.append(Route.productReviews(id))
- }
- }
-}
-
-enum Route: Hashable {
- case userDetail(Int)
- case productDetail(Int)
- case productReviews(Int)
-}
-```
-
-**Parsing URLs into navigation state:**
-```swift
-@Observable
-class DeepLinkRouter {
- var path = NavigationPath()
- var selectedTab: AppTab = .home
-
- func handleDeepLink(_ url: URL) {
- // Parse URL: myapp://tab/search?query=SwiftUI&filter=recent
- guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
- return
- }
-
- // Handle tab switching
- if components.host == "tab",
- let tabName = components.path.split(separator: "/").first {
- switchTab(String(tabName))
- }
-
- // Handle query parameters
- let queryItems = components.queryItems ?? []
- if let query = queryItems.first(where: { $0.name == "query" })?.value {
- navigateToSearch(query: query)
- }
- }
-
- private func switchTab(_ tab: String) {
- switch tab {
- case "home": selectedTab = .home
- case "search": selectedTab = .search
- case "profile": selectedTab = .profile
- default: break
- }
- }
-
- private func navigateToSearch(query: String) {
- selectedTab = .search
- path = NavigationPath()
- path.append(SearchRoute.results(query))
- }
-}
-
-enum AppTab {
- case home
- case search
- case profile
-}
-
-enum SearchRoute: Hashable {
- case results(String)
-}
-```
-
-**Universal Links setup:**
-
-1. **Associated Domains entitlement:** Add in Xcode project capabilities
- - `applinks:example.com`
-
-2. **apple-app-site-association file:** Host at `https://example.com/.well-known/apple-app-site-association`
-```json
-{
- "applinks": {
- "apps": [],
- "details": [
- {
- "appID": "TEAM_ID.com.example.myapp",
- "paths": [
- "/user/*",
- "/product/*"
- ]
- }
- ]
- }
-}
-```
-
-3. **Handle in app:**
-```swift
-@main
-struct MyApp: App {
- @State private var router = Router()
-
- var body: some Scene {
- WindowGroup {
- ContentView()
- .environment(router)
- .onOpenURL { url in
- router.handleUniversalLink(url)
- }
- }
- }
-}
-```
-
-**Custom URL schemes:**
-
-1. **Register scheme in Info.plist:**
-```xml
-CFBundleURLTypes
-
-
- CFBundleURLSchemes
-
- myapp
-
- CFBundleURLName
- com.example.myapp
-
-
-```
-
-2. **Handle custom scheme:**
-```swift
-struct ContentView: View {
- @Environment(Router.self) private var router
-
- var body: some View {
- @Bindable var router = router
-
- NavigationStack(path: $router.path) {
- HomeView()
- .navigationDestination(for: Route.self) { route in
- destinationView(for: route)
- }
- }
- }
-
- @ViewBuilder
- func destinationView(for route: Route) -> some View {
- switch route {
- case .userDetail(let id):
- UserDetailView(userId: id)
- case .productDetail(let id):
- ProductDetailView(productId: id)
- case .productReviews(let id):
- ProductReviewsView(productId: id)
- }
- }
-}
-```
-
-**Security considerations:**
-- Validate all incoming URLs
-- Sanitize parameters before using them
-- Don't expose sensitive functionality via deep links
-- Use Universal Links over custom URL schemes for production (more secure, unique)
-
-
-
-## Coordinator Pattern (Optional)
-
-The Coordinator pattern centralizes navigation logic, decoupling it from views. Use when navigation becomes complex enough to justify the abstraction.
-
-**When to use:**
-- Complex navigation flows with many paths
-- Testable navigation logic separated from views
-- Multiple entry points to the same flow
-- Deep linking with complex routing
-
-**Implementation with @Observable:**
-```swift
-import Observation
-
-@Observable
-class AppCoordinator {
- var path = NavigationPath()
- var sheet: Sheet?
- var fullScreenCover: Cover?
-
- // MARK: - Navigation
-
- func push(_ destination: Destination) {
- path.append(destination)
- }
-
- func pop() {
- guard !path.isEmpty else { return }
- path.removeLast()
- }
-
- func popToRoot() {
- path = NavigationPath()
- }
-
- // MARK: - Sheets
-
- func presentSheet(_ sheet: Sheet) {
- self.sheet = sheet
- }
-
- func dismissSheet() {
- self.sheet = nil
- }
-
- // MARK: - Full Screen
-
- func presentFullScreen(_ cover: Cover) {
- self.fullScreenCover = cover
- }
-
- func dismissFullScreen() {
- self.fullScreenCover = nil
- }
-
- // MARK: - Deep Linking
-
- func handleDeepLink(_ url: URL) {
- guard let components = URLComponents(url: url, resolvingAgainstBaseURL: true) else {
- return
- }
-
- if components.path.contains("/user/"),
- let userId = extractId(from: components.path) {
- popToRoot()
- push(.userDetail(userId))
- }
- }
-
- private func extractId(from path: String) -> Int? {
- let components = path.split(separator: "/")
- return components.last.flatMap { Int($0) }
- }
-}
-
-enum Destination: Hashable {
- case userDetail(Int)
- case settings
- case editProfile
-}
-
-enum Sheet: Identifiable {
- case addUser
- case filter
-
- var id: String {
- switch self {
- case .addUser: return "addUser"
- case .filter: return "filter"
- }
- }
-}
-
-enum Cover: Identifiable {
- case onboarding
- case camera
-
- var id: String {
- switch self {
- case .onboarding: return "onboarding"
- case .camera: return "camera"
- }
- }
-}
-
-// MARK: - Root View
-
-struct RootView: View {
- @State private var coordinator = AppCoordinator()
-
- var body: some View {
- @Bindable var coordinator = coordinator
-
- NavigationStack(path: $coordinator.path) {
- UserListView()
- .navigationDestination(for: Destination.self) { destination in
- destinationView(for: destination)
- }
- }
- .sheet(item: $coordinator.sheet) { sheet in
- sheetView(for: sheet)
- }
- .fullScreenCover(item: $coordinator.fullScreenCover) { cover in
- coverView(for: cover)
- }
- .environment(coordinator)
- .onOpenURL { url in
- coordinator.handleDeepLink(url)
- }
- }
-
- @ViewBuilder
- func destinationView(for destination: Destination) -> some View {
- switch destination {
- case .userDetail(let id):
- UserDetailView(userId: id)
- case .settings:
- SettingsView()
- case .editProfile:
- EditProfileView()
- }
- }
-
- @ViewBuilder
- func sheetView(for sheet: Sheet) -> some View {
- switch sheet {
- case .addUser:
- AddUserView()
- case .filter:
- FilterView()
- }
- }
-
- @ViewBuilder
- func coverView(for cover: Cover) -> some View {
- switch cover {
- case .onboarding:
- OnboardingView()
- case .camera:
- CameraView()
- }
- }
-}
-
-// MARK: - Views using coordinator
-
-struct UserListView: View {
- @Environment(AppCoordinator.self) private var coordinator
-
- var body: some View {
- List {
- ForEach(users) { user in
- Button(user.name) {
- coordinator.push(.userDetail(user.id))
- }
- }
- }
- .navigationTitle("Users")
- .toolbar {
- Button("Add") {
- coordinator.presentSheet(.addUser)
- }
- }
- }
-
- let users = [
- User(id: 1, name: "Alice"),
- User(id: 2, name: "Bob")
- ]
-}
-
-struct UserDetailView: View {
- let userId: Int
- @Environment(AppCoordinator.self) private var coordinator
-
- var body: some View {
- VStack {
- Text("User \(userId)")
-
- Button("Edit Profile") {
- coordinator.push(.editProfile)
- }
-
- Button("Pop to Root") {
- coordinator.popToRoot()
- }
- }
- .navigationTitle("User Detail")
- }
-}
-```
-
-**Trade-offs:**
-- **Pros:** Testable navigation logic, centralized flow control, easier deep linking, decoupled views
-- **Cons:** Additional abstraction layer, more code to maintain, can be overkill for simple apps
-
-**When NOT to use:** Simple apps with linear navigation, apps with fewer than 10 screens, prototypes
-
-
-
-## Navigation State Persistence
-
-Enable state restoration so users return to where they left off when reopening your app.
-
-**Codable NavigationPath:**
-```swift
-struct PersistentNavigation: View {
- @State private var path = NavigationPath()
- @AppStorage("navigationPath") private var navigationPathData: Data?
-
- var body: some View {
- NavigationStack(path: $path) {
- List {
- NavigationLink("Details", value: Route.details)
- NavigationLink("Settings", value: Route.settings)
- }
- .navigationTitle("Home")
- .navigationDestination(for: Route.self) { route in
- routeView(for: route)
- }
- }
- .onAppear {
- restorePath()
- }
- .onChange(of: path) { oldPath, newPath in
- savePath()
- }
- }
-
- @ViewBuilder
- func routeView(for route: Route) -> some View {
- switch route {
- case .details:
- Text("Details")
- case .settings:
- Text("Settings")
- }
- }
-
- func savePath() {
- guard let representation = path.codable else { return }
-
- do {
- let data = try JSONEncoder().encode(representation)
- navigationPathData = data
- } catch {
- print("Failed to save path: \(error)")
- }
- }
-
- func restorePath() {
- guard let data = navigationPathData else { return }
-
- do {
- let representation = try JSONDecoder().decode(
- NavigationPath.CodableRepresentation.self,
- from: data
- )
- path = NavigationPath(representation)
- } catch {
- print("Failed to restore path: \(error)")
- }
- }
-}
-
-enum Route: Hashable, Codable {
- case details
- case settings
-}
-```
-
-**@SceneStorage for restoration:**
-```swift
-struct SceneStorageNavigation: View {
- @SceneStorage("navigationPath") private var pathData: Data?
- @State private var path = NavigationPath()
-
- var body: some View {
- NavigationStack(path: $path) {
- List {
- NavigationLink("Item 1", value: 1)
- NavigationLink("Item 2", value: 2)
- }
- .navigationDestination(for: Int.self) { value in
- DetailView(value: value)
- }
- }
- .task {
- if let data = pathData,
- let representation = try? JSONDecoder().decode(
- NavigationPath.CodableRepresentation.self,
- from: data
- ) {
- path = NavigationPath(representation)
- }
- }
- .onChange(of: path) { _, newPath in
- if let representation = newPath.codable,
- let data = try? JSONEncoder().encode(representation) {
- pathData = data
- }
- }
- }
-}
-```
-
-**Important notes:**
-- Only works if all types in NavigationPath are Codable
-- @SceneStorage cleared when user force-quits app
-- @AppStorage persists across launches but not recommended for large data
-- Test restoration thoroughly (background app, force quit, etc.)
-
-
-
-## Choosing the Right Approach
-
-**Simple app with few screens:** NavigationStack with NavigationLink (user-driven navigation is sufficient)
-
-**Need programmatic navigation:** NavigationStack + NavigationPath in @Observable class stored in @State
-
-**Modal content (settings, forms, detail overlays):** .sheet() for dismissible modals, .fullScreenCover() for immersive content
-
-**Multiple independent sections:** TabView with separate NavigationStack per tab
-
-**Deep linking required:** onOpenURL + NavigationPath (parse URL and manipulate path programmatically)
-
-**Complex navigation flows (10+ screens, multiple entry points):** Coordinator pattern with @Observable coordinator managing NavigationPath and sheet/cover state
-
-**State restoration needed:** NavigationPath.codable with @SceneStorage or @AppStorage
-
-**Platform differences matter:** Check platform in architecture.md, use NavigationSplitView for iPad/macOS multi-column layouts
-
-
-
-## What NOT to Do
-
-
-**Problem:** NavigationView is deprecated in iOS 16+
-
-**Why it's bad:**
-- Missing modern features (programmatic navigation, type-safe routing)
-- Deprecated API that may be removed
-- NavigationStack is more performant and flexible
-
-**Instead:** Use NavigationStack
-```swift
-// WRONG
-NavigationView {
- List { }
-}
-
-// RIGHT
-NavigationStack {
- List { }
-}
-```
-
-
-
-**Problem:** Using @State var showDetail = false for each destination
-
-**Why it's bad:**
-- Doesn't scale beyond 2-3 screens
-- Loses type safety (what data does the destination need?)
-- Can't programmatically navigate deep
-- No navigation history
-
-**Instead:** Use navigationDestination with typed values
-```swift
-// WRONG
-@State private var showUserDetail = false
-@State private var showSettings = false
-@State private var showProfile = false
-
-// RIGHT
-@State private var path = NavigationPath()
-
-NavigationStack(path: $path) {
- Button("Show User") {
- path.append(Route.userDetail(id: 1))
- }
- .navigationDestination(for: Route.self) { route in
- // Handle route
- }
-}
-```
-
-
-
-**Problem:** Storing NavigationPath in child views that need to access it
-
-**Why it's bad:**
-- Child views can't access parent's NavigationPath
-- Forces passing bindings through many levels
-- Breaks encapsulation
-
-**Instead:** Store in @Observable, pass via @Environment
-```swift
-// WRONG
-struct ChildView: View {
- @State private var path = NavigationPath() // Can't access parent's path
-}
-
-// RIGHT
-@Observable
-class Router {
- var path = NavigationPath()
-}
-
-@main
-struct App: App {
- @State private var router = Router()
-
- var body: some Scene {
- WindowGroup {
- ContentView()
- .environment(router)
- }
- }
-}
-
-struct ChildView: View {
- @Environment(Router.self) private var router
-
- var body: some View {
- Button("Navigate") {
- router.path.append(destination)
- }
- }
-}
-```
-
-
-
-**Problem:** Putting navigationDestination inside List, ScrollView, LazyVStack
-
-**Why it's bad:**
-- Destination closures may not be called
-- Lazy loading means modifiers aren't registered
-- Apple explicitly warns against this
-
-**Instead:** Place navigationDestination on NavigationStack or its immediate child
-```swift
-// WRONG
-NavigationStack {
- List {
- ForEach(items) { item in
- NavigationLink(item.name, value: item)
- }
- .navigationDestination(for: Item.self) { item in // ❌ Inside List
- DetailView(item: item)
- }
- }
-}
-
-// RIGHT
-NavigationStack {
- List {
- ForEach(items) { item in
- NavigationLink(item.name, value: item)
- }
- }
- .navigationDestination(for: Item.self) { item in // ✅ Outside List
- DetailView(item: item)
- }
-}
-```
-
-
-
-**Problem:** Using sheets for multi-step flows that should be pushed
-
-**Why it's bad:**
-- Sheets are for modal content, not hierarchical navigation
-- Can't use back button (must dismiss)
-- Breaks user expectations
-- No navigation history
-
-**Instead:** Use NavigationStack for flows, sheets for modals
-```swift
-// WRONG - using sheets for sequential steps
-.sheet(isPresented: $showStep2) {
- Step2View()
- .sheet(isPresented: $showStep3) {
- Step3View() // Nested sheets
- }
-}
-
-// RIGHT - NavigationStack for flows
-NavigationStack(path: $path) {
- Step1View()
- .navigationDestination(for: Step.self) { step in
- switch step {
- case .step2: Step2View()
- case .step3: Step3View()
- }
- }
-}
-
-// RIGHT - Sheets for modals
-.sheet(isPresented: $showSettings) {
- SettingsView() // Self-contained modal
-}
-```
-
-
-
-**Problem:** Forgetting to conform to Hashable for navigationDestination types
-
-**Why it's bad:**
-- Compiler error: navigationDestination requires Hashable
-- NavigationPath can't store non-Hashable types
-
-**Instead:** Always make route types Hashable (and Codable for persistence)
-```swift
-// WRONG
-struct Route {
- let id: Int
-}
-
-// RIGHT
-struct Route: Hashable {
- let id: Int
-}
-
-// EVEN BETTER - also Codable for persistence
-enum Route: Hashable, Codable {
- case detail(id: Int)
- case settings
-}
-```
-
-
-
-**Problem:** Sharing NavigationPath between tabs
-
-**Why it's bad:**
-- Tabs should have independent navigation stacks
-- Switching tabs loses navigation context
-- Breaks expected tab behavior
-
-**Instead:** Each tab gets its own NavigationStack and path
-```swift
-// WRONG
-@State private var path = NavigationPath()
-
-TabView {
- NavigationStack(path: $path) { HomeView() }
- .tabItem { Label("Home", systemImage: "house") }
-
- NavigationStack(path: $path) { SearchView() } // ❌ Shared path
- .tabItem { Label("Search", systemImage: "magnifyingglass") }
-}
-
-// RIGHT
-@State private var homePath = NavigationPath()
-@State private var searchPath = NavigationPath()
-
-TabView {
- NavigationStack(path: $homePath) { HomeView() }
- .tabItem { Label("Home", systemImage: "house") }
-
- NavigationStack(path: $searchPath) { SearchView() } // ✅ Independent
- .tabItem { Label("Search", systemImage: "magnifyingglass") }
-}
-```
-
-
-
-## Sources
-
-- [Hacking with Swift: Programmatic navigation with NavigationStack](https://www.hackingwithswift.com/books/ios-swiftui/programmatic-navigation-with-navigationstack)
-- [AzamSharp: Navigation Patterns in SwiftUI](https://azamsharp.com/2024/07/29/navigation-patterns-in-swiftui.html)
-- [tanaschita: How to use NavigationPath for routing in SwiftUI](https://tanaschita.com/swiftui-navigationpath/)
-- [Swift with Majid: Mastering NavigationStack in SwiftUI. Navigator Pattern](https://swiftwithmajid.com/2022/06/15/mastering-navigationstack-in-swiftui-navigator-pattern/)
-- [Medium: Mastering Navigation in SwiftUI: The 2025 Guide](https://medium.com/@dinaga119/mastering-navigation-in-swiftui-the-2025-guide-to-clean-scalable-routing-bbcb6dbce929)
-- [Swift Anytime: How to use Coordinator Pattern in SwiftUI](https://www.swiftanytime.com/blog/coordinator-pattern-in-swiftui)
-- [SwiftLee: Deeplink URL handling in SwiftUI](https://www.avanderlee.com/swiftui/deeplink-url-handling/)
-- [Michael Long: Advanced Deep Linking in SwiftUI](https://michaellong.medium.com/advanced-deep-linking-in-swiftui-c0085be83e7c)
-- [Swift with Majid: Deep linking for local notifications in SwiftUI](https://swiftwithmajid.com/2024/04/09/deep-linking-for-local-notifications-in-swiftui/)
-- [Sarunw: Bottom Sheet in SwiftUI on iOS 16 with presentationDetents](https://sarunw.com/posts/swiftui-bottom-sheet/)
-- [Apple Developer: presentationDetents(_:)](https://developer.apple.com/documentation/swiftui/view/presentationdetents(_:))
-- [Hacking with Swift: What's new in SwiftUI for iOS 18](https://www.hackingwithswift.com/articles/270/whats-new-in-swiftui-for-ios-18)
-- [iOS Coffee Break: Using SwiftUI's Improved TabView with Sidebar on iOS 18](https://www.ioscoffeebreak.com/issue/issue34)
-- [AppCoda: What's New in SwiftUI for iOS 18](https://www.appcoda.com/swiftui-ios-18/)
-- [Medium: Getting Started with the Improved TabView in iOS 18](https://medium.com/@jpmtech/getting-started-with-the-improved-tabview-in-ios-18-111974b70db9)
-- [Apple Developer: Enhancing your app's content with tab navigation](https://developer.apple.com/documentation/swiftui/enhancing-your-app-content-with-tab-navigation)
-- [Apple Developer: NavigationPath](https://developer.apple.com/documentation/swiftui/navigationpath)
-- [DEV Community: Modern Navigation in SwiftUI](https://dev.to/sebastienlato/modern-navigation-in-swiftui-1c8g)
-- [Medium: Mastering Navigation in SwiftUI Using Coordinator Pattern](https://medium.com/@dikidwid0/mastering-navigation-in-swiftui-using-coordinator-pattern-833396c67db5)
-- [QuickBird Studios: How to Use the Coordinator Pattern in SwiftUI](https://quickbirdstudios.com/blog/coordinator-pattern-in-swiftui/)
diff --git a/src/resources/skills/swiftui/references/networking-async.md b/src/resources/skills/swiftui/references/networking-async.md
deleted file mode 100644
index 8a3601cd2..000000000
--- a/src/resources/skills/swiftui/references/networking-async.md
+++ /dev/null
@@ -1,214 +0,0 @@
-
-SwiftUI networking in 2025 is built around Swift's structured concurrency (async/await) with the @Observable macro for state management. Combine is primarily used for specialized reactive scenarios.
-
-**When to use async/await:**
-- Loading data when views appear (.task modifier)
-- Sequential API calls with dependencies
-- Error handling with do-catch
-- Any new code requiring async operations
-
-**When Combine is still useful:**
-- Complex reactive pipelines (debouncing, throttling)
-- Form validation with multiple interdependent fields
-- Real-time data streams (websockets, timers)
-
-**Core principle:** Use async/await by default. Add Combine only when reactive operators provide clear value.
-
-
-
-## The .task Modifier
-
-**Basic usage:**
-```swift
-struct ArticleView: View {
- @State private var article: Article?
- let articleID: String
-
- var body: some View {
- content
- .task {
- article = try? await fetchArticle(id: articleID)
- }
- }
-}
-```
-
-**With dependency (.task(id:)):**
-```swift
-struct SearchView: View {
- @State private var query = ""
- @State private var results: [Result] = []
-
- var body: some View {
- List(results) { result in Text(result.name) }
- .searchable(text: $query)
- .task(id: query) {
- guard !query.isEmpty else { return }
- try? await Task.sleep(for: .milliseconds(300))
- guard !Task.isCancelled else { return }
- results = (try? await search(query: query)) ?? []
- }
- }
-}
-```
-
-**Key behaviors:**
-- Runs when view appears
-- Auto-cancels on view disappear
-- .task(id:) restarts when dependency changes
-
-
-
-## Async/Await Patterns
-
-**Loading with @Observable:**
-```swift
-@Observable
-@MainActor
-class ArticleViewModel {
- private(set) var state: LoadingState = .idle
-
- func load(id: String) async {
- state = .loading
- do {
- let article = try await apiClient.fetchArticle(id: id)
- state = .loaded(article)
- } catch is CancellationError {
- // Don't update state
- } catch {
- state = .failed(error)
- }
- }
-}
-```
-
-**Parallel calls:**
-```swift
-func loadProfile(id: String) async throws -> Profile {
- let user = try await fetchUser(id: id)
- async let posts = fetchPosts(userID: user.id)
- async let followers = fetchFollowers(userID: user.id)
- return Profile(user: user, posts: try await posts, followers: try await followers)
-}
-```
-
-
-
-## API Client Architecture
-
-```swift
-protocol APIClient {
- func request(_ endpoint: Endpoint) async throws -> T
-}
-
-@MainActor
-final class ProductionAPIClient: APIClient {
- private let baseURL: URL
- private let session: URLSession
-
- func request(_ endpoint: Endpoint) async throws -> T {
- let request = try buildRequest(endpoint)
- let (data, response) = try await session.data(for: request)
-
- guard let httpResponse = response as? HTTPURLResponse,
- (200...299).contains(httpResponse.statusCode) else {
- throw APIError.httpError((response as? HTTPURLResponse)?.statusCode ?? 0)
- }
-
- return try JSONDecoder().decode(T.self, from: data)
- }
-}
-```
-
-
-
-## Loading States
-
-```swift
-enum LoadingState {
- case idle
- case loading
- case loaded(Value)
- case failed(Error)
-
- var isLoading: Bool {
- if case .loading = self { return true }
- return false
- }
-}
-
-struct AsyncContentView: View {
- let state: LoadingState
- let retry: () async -> Void
- @ViewBuilder let content: (Value) -> Content
-
- var body: some View {
- switch state {
- case .idle: Color.clear
- case .loading: ProgressView()
- case .loaded(let value): content(value)
- case .failed(let error):
- ContentUnavailableView("Error", systemImage: "exclamationmark.triangle", description: Text(error.localizedDescription))
- }
- }
-}
-```
-
-
-
-## Error Handling & Retry
-
-**Basic retry:**
-```swift
-func fetchWithRetry(maxRetries: Int = 3, operation: () async throws -> T) async throws -> T {
- var lastError: Error?
- for attempt in 0..
-
-
-## Choosing the Right Approach
-
-**Tied to view lifecycle?** → .task or .task(id:)
-**User-triggered?** → Wrap in explicit Task {}
-**Need reactive operators?** → Combine
-**Loading data?** → Use LoadingState enum
-**Sequential calls?** → async/await naturally
-**Parallel calls?** → async let or TaskGroup
-
-
-
-## What NOT to Do
-
-
-**Problem:** Showing error UI when task is cancelled
-**Instead:** Catch CancellationError separately, don't update state
-
-
-
-**Problem:** Task { await loadData() } inside .task
-**Instead:** .task already creates a Task
-
-
-
-**Problem:** View model updates from background thread
-**Instead:** Mark @Observable view models with @MainActor
-
-
-
-**Problem:** Using ObservableObject/@Published
-**Instead:** Use @Observable macro (iOS 17+)
-
-
diff --git a/src/resources/skills/swiftui/references/performance.md b/src/resources/skills/swiftui/references/performance.md
deleted file mode 100644
index 0f3049732..000000000
--- a/src/resources/skills/swiftui/references/performance.md
+++ /dev/null
@@ -1,1706 +0,0 @@
-# SwiftUI Performance Reference
-
-
-SwiftUI's declarative, data-driven architecture provides automatic UI updates, but this comes with performance implications. Understanding the update cycle, view identity, and optimization strategies enables building responsive apps.
-
-**Core Performance Model:**
-
-1. **State Change**: A property wrapped with @State, @Observable, or similar changes
-2. **Body Recomputation**: SwiftUI evaluates the view's body property
-3. **Diffing**: SwiftUI compares new view hierarchy against previous
-4. **Minimal Updates**: Only changed parts render to screen via Core Animation
-
-**Key Principle**: SwiftUI only recomputes body when dependency values change. Mastering what triggers recomputation and how to minimize it is essential for performance.
-
-**Performance Philosophy**: Profile before optimizing. SwiftUI includes automatic optimizations. Only intervene when profiling identifies actual bottlenecks. Premature optimization adds complexity without benefit.
-
-
-
-**View Identity** determines how SwiftUI tracks views across updates. Identity affects state preservation, transitions, and performance.
-
-## Two Types of Identity
-
-### Structural Identity
-
-SwiftUI identifies views by their position in the view hierarchy. Most common form of identity.
-
-```swift
-// Structural identity - views identified by position
-VStack {
- Text("First") // Identity: VStack > position 0
- Text("Second") // Identity: VStack > position 1
-}
-
-// Problematic: branches change structural identity
-if isLoggedIn {
- ProfileView() // Identity: if branch > ProfileView
-} else {
- LoginView() // Identity: else branch > LoginView
-}
-```
-
-**Best Practice**: Prefer conditional modifiers over branches to preserve identity:
-
-```swift
-// Bad - changes structural identity, loses state
-if isExpanded {
- DetailView(expanded: true)
-} else {
- DetailView(expanded: false)
-}
-
-// Good - preserves structural identity
-DetailView(expanded: isExpanded)
-```
-
-### Explicit Identity
-
-Use the `.id()` modifier to explicitly control identity. SwiftUI treats views with different IDs as completely distinct.
-
-```swift
-// Force view recreation by changing ID
-ScrollView {
- ContentView()
- .id(selectedCategory) // New ID = destroy and recreate
-}
-
-// List items use Identifiable for explicit identity
-struct Item: Identifiable {
- let id: UUID
- let name: String
-}
-
-List(items) { item in
- Text(item.name) // SwiftUI tracks by item.id
-}
-```
-
-**When to Use .id()**:
-
-- Reset view state (form after submission, scroll position)
-- Force view recreation when data fundamentally changes
-- Ensure transitions work correctly
-
-**Performance Impact**: Changing a view's ID destroys the old view and creates a new one, discarding all state. Expensive operation - use judiciously.
-
-## Identity and State Preservation
-
-SwiftUI maintains @State values as long as view identity remains stable:
-
-```swift
-struct CounterView: View {
- @State private var count = 0 // Preserved while identity stable
-
- var body: some View {
- VStack {
- Text("Count: \(count)")
- Button("Increment") { count += 1 }
- }
- }
-}
-
-// Branching destroys identity and @State
-if showCounter {
- CounterView() // count resets to 0 when toggled
-}
-
-// Better: preserve identity with opacity/hidden
-CounterView()
- .opacity(showCounter ? 1 : 0) // State preserved
-```
-
-## Debugging Identity
-
-Use `Self._printChanges()` to see what triggers body recomputation:
-
-```swift
-var body: some View {
- let _ = Self._printChanges() // Xcode console shows changed properties
-
- VStack {
- Text("Content")
- }
-}
-```
-
-
-
-**Lazy containers** create views on-demand as they scroll into view, rather than creating all views upfront.
-
-## Lazy Stack Types
-
-```swift
-// LazyVStack - vertical scrolling
-ScrollView {
- LazyVStack(spacing: 16) {
- ForEach(items) { item in
- ItemRow(item: item) // Created only when visible
- }
- }
-}
-
-// LazyHStack - horizontal scrolling
-ScrollView(.horizontal) {
- LazyHStack(spacing: 16) {
- ForEach(items) { item in
- ItemCard(item: item)
- }
- }
-}
-
-// LazyVGrid - grid layout
-ScrollView {
- LazyVGrid(columns: [
- GridItem(.adaptive(minimum: 150))
- ], spacing: 16) {
- ForEach(items) { item in
- ItemCard(item: item)
- }
- }
-}
-
-// LazyHGrid - horizontal grid
-ScrollView(.horizontal) {
- LazyHGrid(rows: [
- GridItem(.fixed(100)),
- GridItem(.fixed(100))
- ], spacing: 16) {
- ForEach(items) { item in
- ItemCard(item: item)
- }
- }
-}
-```
-
-## Performance Characteristics
-
-**Benefits**:
-- **Reduced Memory**: 80-90% less memory than non-lazy equivalents for large lists
-- **Faster Load**: Milliseconds vs seconds for initial render
-- **Smooth Scrolling**: Maintains 60fps even with hundreds of items
-
-**Tradeoffs**:
-- Views created lazily incur small bookkeeping overhead
-- Once created, views stay in memory (not recycled like UITableView)
-- For very large datasets (thousands of items), List provides view recycling
-
-```swift
-// Memory comparison for 200 items:
-// VStack: ~300MB, 2-3 second load
-// LazyVStack: ~40MB, <100ms load
-// List: ~40MB with view recycling (better for 1000+ items)
-```
-
-## When to Use Lazy Containers
-
-**Use LazyVStack/LazyHStack when**:
-- Scrolling list with dozens to hundreds of items
-- Items contain images, videos, or heavy views
-- Custom animations and transitions required
-- ScrollView directly wraps the stack
-
-**Use List when**:
-- Thousands of items (view recycling needed)
-- Standard list appearance acceptable
-- Platform-native behavior desired
-
-**Avoid Lazy when**:
-- Small number of items (< 20)
-- All views fit on screen without scrolling
-- Lazy overhead exceeds benefit (profile first)
-
-```swift
-// Decision example
-struct ContentView: View {
- let items: [Item]
-
- var body: some View {
- ScrollView {
- if items.count > 50 {
- // Use lazy for large lists
- LazyVStack {
- ForEach(items) { ItemRow(item: $0) }
- }
- } else {
- // Regular stack fine for small lists
- VStack {
- ForEach(items) { ItemRow(item: $0) }
- }
- }
- }
- }
-}
-```
-
-## Lazy Container Best Practices
-
-**Design Lightweight Views**: Lazy loading doesn't eliminate cost of heavy views.
-
-```swift
-// Bad - heavy view defeats lazy loading benefits
-struct ItemRow: View {
- let item: Item
-
- var body: some View {
- VStack {
- AsyncImage(url: item.imageURL) { image in
- image.resizable() // No size limit - uses full resolution
- } placeholder: {
- ProgressView()
- }
- Text(item.longDescription) // Renders all text upfront
- }
- }
-}
-
-// Good - lightweight view
-struct ItemRow: View {
- let item: Item
-
- var body: some View {
- VStack {
- AsyncImage(url: item.imageURL) { image in
- image
- .resizable()
- .aspectRatio(contentMode: .fill)
- .frame(height: 200) // Limit size
- .clipped()
- } placeholder: {
- Color.gray.frame(height: 200)
- }
- Text(item.shortDescription) // Just what's visible
- }
- }
-}
-```
-
-**Pinned Views**: Use for sticky headers/footers.
-
-```swift
-LazyVStack(pinnedViews: [.sectionHeaders]) {
- ForEach(sections) { section in
- Section {
- ForEach(section.items) { item in
- ItemRow(item: item)
- }
- } header: {
- Text(section.title)
- .font(.headline)
- .frame(maxWidth: .infinity, alignment: .leading)
- .background(Color.gray.opacity(0.2))
- }
- }
-}
-```
-
-
-
-Understanding what triggers body recomputation and how to minimize it is critical for performance.
-
-## What Triggers Body Evaluation
-
-SwiftUI evaluates body when:
-
-1. **@State property changes**: View owns the state
-2. **@Binding updates**: Parent changed bound value
-3. **@Observable property accessed in body changes**: Fine-grained observation
-4. **ObservableObject publishes change**: Any @Published property (not fine-grained)
-5. **@Environment value changes**: Environment changed
-6. **Parent view recreates child**: Parent's body evaluated with different child value
-
-```swift
-struct ProfileView: View {
- @State private var name = "User" // Change triggers body
- @State private var age = 25 // Change triggers body
- let id: UUID // Never changes - no trigger
-
- var body: some View {
- let _ = Self._printChanges() // Debug what changed
-
- VStack {
- Text("Name: \(name)") // Depends on name
- Text("Age: \(age)") // Depends on age
- }
- }
-}
-```
-
-## Minimizing Recomputation
-
-### Extract Subviews
-
-Move stable content into separate views to prevent recomputation:
-
-```swift
-// Bad - entire body recomputes on count change
-struct ContentView: View {
- @State private var count = 0
-
- var body: some View {
- VStack {
- ExpensiveHeaderView() // Recomputes unnecessarily
- Text("Count: \(count)")
- Button("Increment") { count += 1 }
- ExpensiveFooterView() // Recomputes unnecessarily
- }
- }
-}
-
-// Good - isolate expensive views
-struct ContentView: View {
- @State private var count = 0
-
- var body: some View {
- VStack {
- StaticHeader() // Separate view - stable identity
- CounterDisplay(count: count) // Only this recomputes
- StaticFooter() // Separate view - stable identity
- }
- }
-}
-
-struct StaticHeader: View {
- var body: some View {
- ExpensiveHeaderView() // Body only called once
- }
-}
-```
-
-### Avoid Expensive Computations in Body
-
-```swift
-// Bad - recalculates on every body evaluation
-struct ListView: View {
- let items: [Item]
-
- var body: some View {
- let sortedItems = items.sorted { $0.date > $1.date } // Expensive!
- List(sortedItems) { item in
- Text(item.name)
- }
- }
-}
-
-// Good - compute once, cache result
-struct ListView: View {
- let items: [Item]
-
- private var sortedItems: [Item] {
- items.sorted { $0.date > $1.date }
- }
-
- var body: some View {
- List(sortedItems) { item in
- Text(item.name)
- }
- }
-}
-
-// Better - compute outside view if possible
-struct ListView: View {
- let sortedItems: [Item] // Parent sorted once
-
- var body: some View {
- List(sortedItems) { item in
- Text(item.name)
- }
- }
-}
-```
-
-### Use Equatable for Custom Comparison
-
-Tell SwiftUI exactly when to recompute by conforming to Equatable:
-
-```swift
-struct ItemDetailView: View, Equatable {
- let item: Item
- let metadata: Metadata // Large, rarely changes
-
- static func == (lhs: ItemDetailView, rhs: ItemDetailView) -> Bool {
- lhs.item.id == rhs.item.id // Only recompute if item ID changes
- // Ignores metadata changes
- }
-
- var body: some View {
- VStack {
- Text(item.name)
- Text(metadata.description)
- }
- }
-}
-
-// Use with .equatable() modifier
-ParentView {
- ItemDetailView(item: item, metadata: metadata)
- .equatable() // Uses custom == for comparison
-}
-```
-
-### Scope Data Sources Appropriately
-
-```swift
-// Bad - entire hierarchy recomputes
-struct AppView: View {
- @State private var settings = AppSettings() // Top-level state
-
- var body: some View {
- NavigationStack {
- ContentView() // Recomputes when settings change
- .environment(settings)
- }
- }
-}
-
-// Good - state lives close to where it's used
-struct SettingsButton: View {
- @State private var showSettings = false // Local state
-
- var body: some View {
- Button("Settings") { showSettings = true }
- .sheet(isPresented: $showSettings) {
- SettingsView()
- }
- }
-}
-```
-
-## Understanding Body Evaluation vs Rendering
-
-**Critical distinction**: Body evaluation ≠ rendering to screen.
-
-```swift
-// Body evaluated frequently...
-struct CounterView: View {
- @State private var count = 0
-
- var body: some View {
- // This code runs on every evaluation
- VStack {
- Text("Count: \(count)") // ...but SwiftUI only renders if text changed
- Button("Increment") { count += 1 }
- }
- }
-}
-```
-
-SwiftUI evaluates body, then diffs the result. If nothing changed, no rendering occurs. This is why expensive computations hurt even if output is identical.
-
-
-
-**@Observable** (iOS 17+) provides superior performance compared to ObservableObject through fine-grained change tracking.
-
-## ObservableObject Limitations
-
-```swift
-// ObservableObject - coarse-grained updates
-class UserSettings: ObservableObject {
- @Published var username = "User"
- @Published var theme = "Light"
- @Published var notifications = true
-}
-
-struct ProfileView: View {
- @ObservedObject var settings: UserSettings
-
- var body: some View {
- VStack {
- Text(settings.username) // Only reads username...
- }
- // ...but body recomputes when theme or notifications change!
- }
-}
-```
-
-**Problem**: If ANY @Published property changes, ALL views observing the object recompute, regardless of which properties they actually read.
-
-## @Observable Solution
-
-```swift
-// @Observable - fine-grained updates
-@Observable
-class UserSettings {
- var username = "User"
- var theme = "Light"
- var notifications = true
-}
-
-struct ProfileView: View {
- @State var settings: UserSettings
-
- var body: some View {
- VStack {
- Text(settings.username) // Only reads username...
- }
- // ...body only recomputes when username changes!
- }
-}
-```
-
-**Benefit**: Body only evaluates when properties actually accessed in body change. Automatic, compiler-generated tracking.
-
-## Performance Impact
-
-Real-world measurements:
-
-- **80-90% fewer body evaluations** for views reading subset of properties
-- **No Combine overhead**: @Observable uses Swift's observation system, not Combine
-- **Automatic optimization**: No manual effort to minimize updates
-
-```swift
-// Performance comparison
-@Observable
-class DataStore {
- var items: [Item] = [] // Changes frequently
- var settings: Settings = .default // Changes rarely
-}
-
-struct ItemListView: View {
- @State var store: DataStore
-
- var body: some View {
- // With ObservableObject: recomputes on settings change (unnecessary)
- // With @Observable: only recomputes on items change (correct)
- List(store.items) { item in
- ItemRow(item: item)
- }
- }
-}
-```
-
-## Migration Guidelines
-
-**Use @Observable for new code**. It's simpler and faster:
-
-```swift
-// Old pattern - remove
-class ViewModel: ObservableObject {
- @Published var name = ""
- @Published var count = 0
-}
-
-struct OldView: View {
- @StateObject private var viewModel = ViewModel() // ObservableObject
-}
-
-// New pattern - use
-@Observable
-class ViewModel {
- var name = ""
- var count = 0
-}
-
-struct NewView: View {
- @State private var viewModel = ViewModel() // @Observable
-}
-```
-
-**Key differences**:
-
-1. No ObservableObject conformance
-2. No @Published wrapper
-3. Use @State (not @StateObject) for ownership
-4. Use @Bindable for bindings
-
-```swift
-@Observable
-class FormData {
- var name = ""
- var email = ""
-}
-
-struct FormView: View {
- @State private var formData = FormData()
-
- var body: some View {
- Form {
- // Need @Bindable for bindings
- TextField("Name", text: $formData.name)
- TextField("Email", text: $formData.email)
- }
- }
-}
-
-// Alternative: @Bindable parameter
-struct FormFields: View {
- @Bindable var formData: FormData
-
- var body: some View {
- Form {
- TextField("Name", text: $formData.name)
- TextField("Email", text: $formData.email)
- }
- }
-}
-```
-
-## Important: @State Behavior Difference
-
-Critical difference between @StateObject and @State:
-
-```swift
-// ObservableObject with @StateObject
-class OldModel: ObservableObject {
- init() { print("OldModel init") }
-}
-
-struct OldView: View {
- @StateObject private var model = OldModel()
- // Prints "OldModel init" ONCE - @StateObject preserves across view recreations
-}
-
-// @Observable with @State
-@Observable
-class NewModel {
- init() { print("NewModel init") }
-}
-
-struct NewView: View {
- @State private var model = NewModel()
- // Prints "NewModel init" on EVERY view recreation!
- // SwiftUI preserves the instance, but re-runs initializer
-}
-```
-
-**Best practice**: Only use @State for @Observable at the view that creates the instance. Pass to child views without @State:
-
-```swift
-struct ParentView: View {
- @State private var model = DataModel() // Owner uses @State
-
- var body: some View {
- ChildView(model: model) // Child receives plain reference
- }
-}
-
-struct ChildView: View {
- let model: DataModel // NOT @State
-
- var body: some View {
- Text(model.name) // Still reactive - automatic observation
- }
-}
-```
-
-
-
-Image loading and rendering are common performance bottlenecks. SwiftUI provides AsyncImage for remote images, but requires careful optimization.
-
-## AsyncImage Basics
-
-```swift
-// Basic AsyncImage
-AsyncImage(url: URL(string: "https://example.com/image.jpg")) { image in
- image
- .resizable()
- .aspectRatio(contentMode: .fill)
-} placeholder: {
- ProgressView()
-}
-```
-
-## Critical Issue: AsyncImage Does Not Cache
-
-**Important**: AsyncImage does NOT cache images between screen loads. Scrolling an image off-screen and back may trigger a new network request.
-
-```swift
-// Problem: re-downloads on scroll
-ScrollView {
- LazyVStack {
- ForEach(items) { item in
- AsyncImage(url: item.imageURL) { image in
- image.resizable()
- } placeholder: {
- ProgressView()
- }
- // Scrolls off screen -> image released
- // Scrolls back on screen -> downloads again!
- }
- }
-}
-```
-
-## Solution 1: Configure URLCache
-
-AsyncImage uses URLSession.shared, which respects URLCache. Configure cache size:
-
-```swift
-// In @main App init
-@main
-struct MyApp: App {
- init() {
- // Configure URLCache for AsyncImage
- URLCache.shared.memoryCapacity = 50_000_000 // 50 MB memory
- URLCache.shared.diskCapacity = 1_000_000_000 // 1 GB disk
- }
-
- var body: some Scene {
- WindowGroup {
- ContentView()
- }
- }
-}
-```
-
-**Limitation**: URLCache respects HTTP cache headers. If server doesn't provide appropriate headers, caching may not work as expected.
-
-## Solution 2: Build Custom Cached AsyncImage
-
-Use NSCache for in-memory caching with custom control:
-
-```swift
-// Image cache manager
-@Observable
-class ImageCache {
- static let shared = ImageCache()
- private var cache = NSCache()
-
- func get(url: String) -> UIImage? {
- cache.object(forKey: url as NSString)
- }
-
- func set(url: String, image: UIImage) {
- cache.setObject(image, forKey: url as NSString)
- }
-}
-
-// Cached AsyncImage view
-struct CachedAsyncImage: View {
- let url: URL?
- let content: (Image) -> Content
- let placeholder: () -> Placeholder
-
- @State private var image: UIImage?
-
- var body: some View {
- Group {
- if let image {
- content(Image(uiImage: image))
- } else {
- placeholder()
- .task {
- await loadImage()
- }
- }
- }
- }
-
- private func loadImage() async {
- guard let url else { return }
-
- // Check cache first
- if let cached = ImageCache.shared.get(url: url.absoluteString) {
- image = cached
- return
- }
-
- // Download if not cached
- do {
- let (data, _) = try await URLSession.shared.data(from: url)
- if let downloaded = UIImage(data: data) {
- ImageCache.shared.set(url: url.absoluteString, image: downloaded)
- image = downloaded
- }
- } catch {
- print("Failed to load image: \(error)")
- }
- }
-}
-```
-
-## Solution 3: Use Third-Party Libraries
-
-For production apps, consider mature image loading libraries:
-
-- **Nuke**: High-performance image loading with aggressive caching
-- **Kingfisher**: Feature-rich with SwiftUI support
-- **SDWebImage**: Battle-tested, widely used
-
-```swift
-// Example with third-party library
-import Nuke
-import NukeUI
-
-LazyImage(url: item.imageURL) { state in
- if let image = state.image {
- image.resizable().aspectRatio(contentMode: .fill)
- } else {
- ProgressView()
- }
-}
-```
-
-## Image Sizing Best Practices
-
-**Always specify image dimensions** to prevent SwiftUI from using full resolution:
-
-```swift
-// Bad - loads full resolution image
-AsyncImage(url: imageURL) { image in
- image.resizable() // Loads 4K image for 100x100 display
-}
-
-// Good - constrains size
-AsyncImage(url: imageURL) { image in
- image
- .resizable()
- .aspectRatio(contentMode: .fill)
- .frame(width: 100, height: 100) // Limits memory usage
- .clipped()
-}
-
-// Better - serve appropriately sized images
-// Use CDN or server-side resizing to deliver thumbnails, not full resolution
-AsyncImage(url: item.thumbnailURL) { image in // 200x200 version
- image
- .resizable()
- .aspectRatio(contentMode: .fill)
- .frame(width: 100, height: 100)
-}
-```
-
-## Prefetching for Scrolling
-
-Prefetch images just before they become visible:
-
-```swift
-struct OptimizedImageList: View {
- let items: [Item]
-
- var body: some View {
- ScrollView {
- LazyVStack {
- ForEach(items) { item in
- CachedAsyncImage(url: item.imageURL) { image in
- image.resizable()
- } placeholder: {
- Color.gray
- }
- .onAppear {
- // Prefetch next items
- prefetchNextImages(after: item)
- }
- }
- }
- }
- }
-
- private func prefetchNextImages(after item: Item) {
- guard let index = items.firstIndex(where: { $0.id == item.id }) else { return }
- let nextItems = items.dropFirst(index + 1).prefix(3)
-
- Task {
- for nextItem in nextItems {
- // Start download without displaying
- _ = try? await URLSession.shared.data(from: nextItem.imageURL)
- }
- }
- }
-}
-```
-
-
-
-Xcode's Instruments app provides powerful profiling for SwiftUI performance analysis.
-
-## Starting a Profile Session
-
-1. Build in Release mode: Product > Profile (Cmd+I)
-2. Select **SwiftUI** template (Xcode 16+) or **Time Profiler** (earlier versions)
-3. **Always profile on real device**, never simulator
-
-```bash
-# Release mode optimizations match production
-# Simulator performance doesn't reflect real device
-```
-
-## SwiftUI Instruments Template (Xcode 16+)
-
-The SwiftUI template includes specialized tracks:
-
-### 1. Update Groups Lane
-
-Shows when SwiftUI is performing update work. If CPU spikes when this lane is empty, the bottleneck is outside SwiftUI (networking, data processing, etc.).
-
-### 2. View Body Lane
-
-Tracks how often view body properties are evaluated.
-
-**Key metrics**:
-- **Count**: Number of times body evaluated
-- **Avg Duration**: Average time per evaluation
-- **Total Duration**: Cumulative time
-
-**What to look for**:
-- Views with high count but low duration: Unnecessary evaluations (fix with Equatable, extract subviews)
-- Views with high duration: Expensive computations in body (move outside body)
-
-### 3. View Properties Lane
-
-Shows every view property change. Property updates are more frequent than body updates (SwiftUI batches multiple property changes into single body update).
-
-**Use to identify**:
-- Properties updating more frequently than expected
-- Cascading updates from parent to children
-
-### 4. Core Animation Commits Lane
-
-Shows when SwiftUI commits changes to Core Animation for rendering. Expensive commits indicate actual pixel changes on screen.
-
-**Correlation**:
-- Many body evaluations + few commits = good (SwiftUI diffing working)
-- Many commits = actual rendering work (investigate why so many pixel changes)
-
-### 5. Time Profiler Lane
-
-Shows CPU usage by function. Reveals which code is running and how long.
-
-**How to use**:
-1. Record profile session
-2. Stop after representative user interaction
-3. Look for heavy call stacks
-4. Drill into SwiftUI view types to find bottlenecks
-
-## Analyzing Body Evaluations
-
-After profiling, Instruments shows "All Updates Summary":
-
-```swift
-// Example summary
-ViewType Count Avg Duration Total Duration
-------------------------------------------------------------------
-ProductListView 456 2.3ms 1,048ms
-ProductCard 2,340 0.8ms 1,872ms
-HeaderView 1 0.2ms 0.2ms
-```
-
-**Interpretation**:
-- ProductListView: 456 evaluations in one session is suspicious - should be much fewer
-- ProductCard: High count expected (many instances), but 0.8ms average is acceptable
-- HeaderView: 1 evaluation is ideal for static content
-
-## Finding Excessive Updates
-
-Use Cmd+1 or select "Summary: All Updates" from jump bar:
-
-```swift
-// Views updating the most appear at top
-// Click view name -> see what triggered updates
-```
-
-Look for:
-- Static views updating repeatedly (should be 1-2 times)
-- Views updating when dependencies haven't changed
-- Cascading updates (parent change triggers all children)
-
-## Debugging with _printChanges()
-
-Combine Instruments with runtime debugging:
-
-```swift
-struct ProblematicView: View {
- @State private var count = 0
- @State private var name = "Test"
-
- var body: some View {
- let _ = Self._printChanges() // Prints to Xcode console
-
- VStack {
- Text("Count: \(count)")
- Text("Name: \(name)")
- }
- }
-}
-
-// Console output when count changes:
-// ProblematicView: @self, @identity, _count changed.
-```
-
-## Common Findings and Solutions
-
-| Finding | Cause | Solution |
-|---------|-------|----------|
-| Header view updates 100+ times | Parent state change | Extract to separate view |
-| Image view high duration | Full resolution loading | Constrain frame size |
-| List scrolling causes body storm | Expensive row computations | Move computation outside body |
-| State changes cause app-wide updates | Top-level state | Move state closer to usage |
-
-## Weekly Profiling Practice
-
-Profile incrementally to catch performance regressions early:
-
-```swift
-// Profiling routine
-1. Profile baseline before changes
-2. Implement feature
-3. Profile again
-4. Compare metrics
-5. Fix regressions before merging
-```
-
-Small, consistent profiling catches issues when they're easy to fix, rather than debugging performance problems across large changesets.
-
-
-
-Specific techniques for optimizing SwiftUI performance.
-
-## 1. Task Prioritization with Priority
-
-Control async task priority to keep UI responsive:
-
-```swift
-struct DataLoadingView: View {
- @State private var essentialData: [Item] = []
- @State private var optionalData: [Detail] = []
-
- var body: some View {
- VStack {
- List(essentialData) { item in
- ItemRow(item: item)
- }
- }
- .task(priority: .high) {
- // Load critical data first
- essentialData = await dataStore.loadEssentialData()
- }
- .task(priority: .low) {
- // Load nice-to-have data later
- optionalData = await dataStore.loadOptionalData()
- }
- }
-}
-```
-
-## 2. Pagination for Large Datasets
-
-Load data in chunks as user scrolls:
-
-```swift
-struct PaginatedListView: View {
- @State private var items: [Item] = []
- @State private var page = 1
- @State private var isLoading = false
-
- var body: some View {
- ScrollView {
- LazyVStack {
- ForEach(items) { item in
- ItemRow(item: item)
- .onAppear {
- loadMoreIfNeeded(currentItem: item)
- }
- }
-
- if isLoading {
- ProgressView()
- }
- }
- }
- .task {
- await loadPage()
- }
- }
-
- private func loadMoreIfNeeded(currentItem: Item) {
- guard let index = items.firstIndex(where: { $0.id == currentItem.id }) else { return }
-
- // Load next page when reaching last 5 items
- if index >= items.count - 5 && !isLoading {
- Task {
- await loadPage()
- }
- }
- }
-
- private func loadPage() async {
- guard !isLoading else { return }
- isLoading = true
-
- let newItems = await dataStore.loadItems(page: page)
- items.append(contentsOf: newItems)
- page += 1
-
- isLoading = false
- }
-}
-```
-
-## 3. Debouncing Expensive Updates
-
-Delay expensive operations while user is typing:
-
-```swift
-struct SearchView: View {
- @State private var searchText = ""
- @State private var searchResults: [Item] = []
- @State private var searchTask: Task?
-
- var body: some View {
- VStack {
- TextField("Search", text: $searchText)
- .onChange(of: searchText) { oldValue, newValue in
- // Cancel previous search
- searchTask?.cancel()
-
- // Debounce: wait 300ms before searching
- searchTask = Task {
- try? await Task.sleep(for: .milliseconds(300))
- guard !Task.isCancelled else { return }
- await performSearch(query: newValue)
- }
- }
-
- List(searchResults) { result in
- Text(result.name)
- }
- }
- }
-
- private func performSearch(query: String) async {
- searchResults = await searchService.search(query)
- }
-}
-```
-
-## 4. Drawing Performance with Canvas
-
-For complex custom drawing, use Canvas instead of GeometryReader and Path:
-
-```swift
-// Slow - triggers relayout frequently
-struct SlowGraph: View {
- let data: [Double]
-
- var body: some View {
- GeometryReader { geometry in
- Path { path in
- // Complex path drawing
- for (index, value) in data.enumerated() {
- let x = CGFloat(index) * geometry.size.width / CGFloat(data.count)
- let y = geometry.size.height * (1 - CGFloat(value))
- if index == 0 {
- path.move(to: CGPoint(x: x, y: y))
- } else {
- path.addLine(to: CGPoint(x: x, y: y))
- }
- }
- }
- .stroke(Color.blue, lineWidth: 2)
- }
- }
-}
-
-// Fast - optimized drawing
-struct FastGraph: View {
- let data: [Double]
-
- var body: some View {
- Canvas { context, size in
- var path = Path()
- for (index, value) in data.enumerated() {
- let x = CGFloat(index) * size.width / CGFloat(data.count)
- let y = size.height * (1 - CGFloat(value))
- if index == 0 {
- path.move(to: CGPoint(x: x, y: y))
- } else {
- path.addLine(to: CGPoint(x: x, y: y))
- }
- }
- context.stroke(path, with: .color(.blue), lineWidth: 2)
- }
- }
-}
-```
-
-## 5. Reduce Modifier Overhead
-
-Combine modifiers when possible:
-
-```swift
-// Multiple modifier evaluations
-Text("Hello")
- .foregroundStyle(.blue)
- .font(.headline)
- .padding()
- .background(.gray)
- .cornerRadius(8)
-
-// Combined where possible - no performance gain in most cases,
-// but clearer code. SwiftUI optimizes modifier chains automatically.
-// Real optimization: avoid conditional modifiers if value doesn't change.
-
-// Inefficient - creates new modifier on every body evaluation
-.opacity(isVisible ? 1.0 : 1.0) // Condition always results in same value
-
-// Efficient - only apply when needed
-.opacity(isVisible ? 1.0 : 0.0)
-```
-
-## 6. PreferenceKey for Bottom-Up Communication
-
-Use PreferenceKey instead of @Binding for child-to-parent data flow when performance matters:
-
-```swift
-struct SizePreferenceKey: PreferenceKey {
- static var defaultValue: CGSize = .zero
- static func reduce(value: inout CGSize, nextValue: () -> CGSize) {
- value = nextValue()
- }
-}
-
-struct ParentView: View {
- @State private var childSize: CGSize = .zero
-
- var body: some View {
- VStack {
- Text("Child size: \(childSize.width) x \(childSize.height)")
-
- ChildView()
- .onPreferenceChange(SizePreferenceKey.self) { size in
- childSize = size
- }
- }
- }
-}
-
-struct ChildView: View {
- var body: some View {
- Text("Hello")
- .background(
- GeometryReader { geometry in
- Color.clear.preference(
- key: SizePreferenceKey.self,
- value: geometry.size
- )
- }
- )
- }
-}
-```
-
-
-
-When to investigate performance and what to optimize.
-
-## Should You Optimize?
-
-```
-Is there a user-facing performance issue?
-├─ No → Don't optimize
-└─ Yes → Continue
-
-Have you profiled with Instruments?
-├─ No → Profile first (never optimize without data)
-└─ Yes → Continue
-
-Did profiling identify a specific bottleneck?
-├─ No → Issue might not be SwiftUI (check networking, data layer)
-└─ Yes → Continue
-
-Is the bottleneck in SwiftUI view updates?
-├─ No → Optimize data layer, networking, image loading
-└─ Yes → Continue to optimization strategies
-```
-
-## Optimization Priority
-
-**1. High Impact, Low Effort**:
-- Switch VStack to LazyVStack for long lists
-- Configure URLCache for AsyncImage
-- Extract static subviews from frequently updating views
-
-**2. High Impact, Medium Effort**:
-- Replace ObservableObject with @Observable
-- Implement pagination for large datasets
-- Add custom Equatable to expensive views
-
-**3. Medium Impact, Low Effort**:
-- Debounce text field updates
-- Use task priority for non-critical work
-- Constrain image sizes with frame modifiers
-
-**4. Medium Impact, High Effort**:
-- Build custom cached image loading
-- Rewrite complex views to reduce body complexity
-- Implement view recycling for very large datasets
-
-**5. Low Priority** (only if profiling shows specific issue):
-- Optimize modifier ordering
-- Use Canvas for complex drawing
-- PreferenceKey instead of Binding
-
-## Red Flags Requiring Optimization
-
-| Symptom | Likely Cause | Action |
-|---------|--------------|--------|
-| Scrolling stutters | Heavy row views | Profile, use lazy loading, simplify rows |
-| Typing lags | Expensive search on every keystroke | Debounce, move work off main thread |
-| Navigation slow | Loading all data upfront | Implement pagination, async loading |
-| App hangs on launch | Too much work in view init | Move to task, use loading states |
-| Memory growing unbounded | Images not releasing | Implement image cache with limits |
-
-## Performance Targets
-
-**Scrolling**: Maintain 60fps (16.67ms per frame)
-- Budget ~10ms for SwiftUI updates
-- Budget ~6ms for rendering
-
-**Interactions**: Respond within 100ms
-- User perceives instant response < 100ms
-- 100-300ms feels sluggish
-- \> 300ms feels broken
-
-**Launch**: Show content within 1 second
-- Use skeleton screens / placeholders
-- Load critical content first, optional content later
-
-
-
-Common performance mistakes and how to avoid them.
-
-## 1. Using @State with Reference Types
-
-**Problem**: @State creates new instance on every view recreation when used with classes.
-
-```swift
-// Wrong - creates new instance repeatedly
-struct BadView: View {
- @State private var viewModel = ViewModel() // ViewModel is a class
-
- var body: some View {
- Text(viewModel.text)
- }
-}
-
-// Correct - use @Observable and @State for iOS 17+
-@Observable
-class ViewModel {
- var text = "Hello"
-}
-
-struct GoodView: View {
- @State private var viewModel = ViewModel()
-
- var body: some View {
- Text(viewModel.text)
- }
-}
-
-// Alternative for iOS 16- - use @StateObject with ObservableObject
-class LegacyViewModel: ObservableObject {
- @Published var text = "Hello"
-}
-
-struct LegacyView: View {
- @StateObject private var viewModel = LegacyViewModel()
-
- var body: some View {
- Text(viewModel.text)
- }
-}
-```
-
-## 2. Overusing AnyView
-
-**Problem**: Type erasure prevents SwiftUI from diffing efficiently, forcing complete view recreation.
-
-```swift
-// Wrong - loses type information
-func makeView(type: ViewType) -> some View {
- switch type {
- case .text:
- return AnyView(Text("Hello"))
- case .image:
- return AnyView(Image(systemName: "star"))
- }
-}
-
-// Correct - preserve types with @ViewBuilder
-@ViewBuilder
-func makeView(type: ViewType) -> some View {
- switch type {
- case .text:
- Text("Hello")
- case .image:
- Image(systemName: "star")
- }
-}
-
-// Alternative - use Group for conditional views
-var body: some View {
- Group {
- if showText {
- Text("Hello")
- } else {
- Image(systemName: "star")
- }
- }
-}
-```
-
-## 3. Creating New Objects in Body
-
-**Problem**: Every body evaluation creates new instances, preventing SwiftUI from recognizing stable values.
-
-```swift
-// Wrong - creates new DateFormatter on every body evaluation
-struct BadDateView: View {
- let date: Date
-
- var body: some View {
- let formatter = DateFormatter() // New instance every time!
- formatter.dateStyle = .medium
- return Text(formatter.string(from: date))
- }
-}
-
-// Correct - create once, reuse
-struct GoodDateView: View {
- let date: Date
-
- private static let formatter: DateFormatter = {
- let f = DateFormatter()
- f.dateStyle = .medium
- return f
- }()
-
- var body: some View {
- Text(Self.formatter.string(from: date))
- }
-}
-
-// Alternative - use built-in formatters
-struct BetterDateView: View {
- let date: Date
-
- var body: some View {
- Text(date, style: .date) // SwiftUI handles formatting
- }
-}
-```
-
-## 4. Branching on View State Instead of Modifiers
-
-**Problem**: Branches change structural identity, losing state and triggering transitions.
-
-```swift
-// Wrong - structural identity changes on toggle
-struct BadToggleView: View {
- @State private var isExpanded = false
-
- var body: some View {
- if isExpanded {
- ExpandedContentView() // Destroyed on collapse
- } else {
- CollapsedContentView() // Destroyed on expand
- }
- }
-}
-
-// Correct - preserve identity with conditional modifiers
-struct GoodToggleView: View {
- @State private var isExpanded = false
-
- var body: some View {
- ContentView(isExpanded: isExpanded) // Same view, different state
- }
-}
-
-// Alternative - use opacity/frame to hide
-struct AlternativeToggleView: View {
- @State private var isExpanded = false
-
- var body: some View {
- VStack {
- HeaderView()
-
- DetailView()
- .frame(height: isExpanded ? nil : 0) // Collapse without destroying
- .opacity(isExpanded ? 1 : 0)
- }
- }
-}
-```
-
-## 5. Excessive GeometryReader Usage
-
-**Problem**: GeometryReader recalculates on every layout change, triggering cascade of updates.
-
-```swift
-// Wrong - unnecessary GeometryReader
-struct BadLayout: View {
- var body: some View {
- GeometryReader { geometry in
- VStack {
- Text("Width: \(geometry.size.width)")
- .frame(width: geometry.size.width * 0.8) // Could use .frame(maxWidth:)
- }
- }
- }
-}
-
-// Correct - use frame modifiers
-struct GoodLayout: View {
- var body: some View {
- VStack {
- Text("Responsive width")
- .frame(maxWidth: .infinity) // Fills available space
- .padding(.horizontal) // 80% width effect
- }
- }
-}
-
-// Use GeometryReader only when truly needed
-struct ValidGeometryUse: View {
- var body: some View {
- GeometryReader { geometry in
- // Valid: need actual size for custom drawing
- CustomShape(size: geometry.size)
- }
- }
-}
-```
-
-## 6. Not Using Lazy Containers for Long Lists
-
-**Problem**: Non-lazy stacks create all views immediately, consuming excessive memory.
-
-```swift
-// Wrong - loads all 1000 items immediately
-ScrollView {
- VStack {
- ForEach(0..<1000) { index in
- HeavyItemView(index: index) // All 1000 created at once
- }
- }
-}
-
-// Correct - lazy loading
-ScrollView {
- LazyVStack {
- ForEach(0..<1000) { index in
- HeavyItemView(index: index) // Created as scrolled into view
- }
- }
-}
-```
-
-## 7. Performing Expensive Work on Main Thread
-
-**Problem**: Blocking main thread makes UI unresponsive.
-
-```swift
-// Wrong - expensive work blocks UI
-struct BadDataView: View {
- @State private var data: [Item] = []
-
- var body: some View {
- List(data) { item in
- Text(item.name)
- }
- .onAppear {
- // Blocks UI while loading
- data = loadDataFromDisk() // Expensive!
- }
- }
-}
-
-// Correct - async work off main thread
-struct GoodDataView: View {
- @State private var data: [Item] = []
- @State private var isLoading = true
-
- var body: some View {
- Group {
- if isLoading {
- ProgressView()
- } else {
- List(data) { item in
- Text(item.name)
- }
- }
- }
- .task {
- // Runs on background thread
- data = await loadDataAsync()
- isLoading = false
- }
- }
-}
-```
-
-## 8. Using ObservableObject Without Scoping Published Properties
-
-**Problem**: Views recompute when any @Published property changes, even ones they don't use.
-
-```swift
-// Problematic - view recomputes on all changes
-class AppState: ObservableObject {
- @Published var userProfile: User? // Changes rarely
- @Published var unreadCount: Int = 0 // Changes frequently
- @Published var networkStatus: Status = .online // Changes frequently
-}
-
-struct ProfileView: View {
- @ObservedObject var appState: AppState
-
- var body: some View {
- // Only uses userProfile, but recomputes on unreadCount changes!
- Text(appState.userProfile?.name ?? "")
- }
-}
-
-// Solution 1: Use @Observable (iOS 17+) for fine-grained observation
-@Observable
-class AppState {
- var userProfile: User? // ProfileView only observes this
- var unreadCount: Int = 0
- var networkStatus: Status = .online
-}
-
-// Solution 2: Split into focused ObservableObjects
-class UserState: ObservableObject {
- @Published var profile: User?
-}
-
-class NotificationState: ObservableObject {
- @Published var unreadCount: Int = 0
-}
-
-struct ProfileView: View {
- @ObservedObject var userState: UserState // Only observes relevant state
-
- var body: some View {
- Text(userState.profile?.name ?? "")
- }
-}
-```
-
-These anti-patterns account for the majority of SwiftUI performance issues. Profiling with Instruments reveals which patterns affect your specific app.
-
-
----
-
-## Sources
-
-- [Optimizing SwiftUI Performance: Best Practices](https://medium.com/@garejakirit/optimizing-swiftui-performance-best-practices-93b9cc91c623)
-- [Demystify SwiftUI performance - WWDC23](https://developer.apple.com/videos/play/wwdc2023/10160/)
-- [Making our production SwiftUI app 100x faster — Clay](https://clay.earth/stories/production-swiftui-performance-increase)
-- [How the SwiftUI View Lifecycle and Identity work - DoorDash Engineering](https://doordash.engineering/2022/05/31/how-the-swiftui-view-lifecycle-and-identity-work/)
-- [Identity in SwiftUI - Geek Culture](https://medium.com/geekculture/identity-in-swiftui-6aacf8f587d9)
-- [Demystify SwiftUI - WWDC21](https://developer.apple.com/videos/play/wwdc2021/10022/)
-- [id(_): Identifying SwiftUI Views - The SwiftUI Lab](https://swiftui-lab.com/swiftui-id/)
-- [How to use Instruments to profile your SwiftUI code - Hacking with Swift](https://www.hackingwithswift.com/quick-start/swiftui/how-to-use-instruments-to-profile-your-swiftui-code-and-identify-slow-layouts)
-- [Profiling SwiftUI app using Instruments - Swift with Majid](https://swiftwithmajid.com/2021/01/20/profiling-swiftui-app-using-instruments/)
-- [@Observable Macro performance increase over ObservableObject](https://www.avanderlee.com/swiftui/observable-macro-performance-increase-observableobject/)
-- [@Observable vs ObservableObject in SwiftUI - Malcolm Hall](https://www.malcolmhall.com/2024/04/22/observable-vs-observableobject-in-swiftui/)
-- [Tuning Lazy Stacks and Grids in SwiftUI: A Performance Guide](https://medium.com/@wesleymatlock/tuning-lazy-stacks-and-grids-in-swiftui-a-performance-guide-2fb10786f76a)
-- [Tips and Considerations for Using Lazy Containers in SwiftUI](https://fatbobman.com/en/posts/tips-and-considerations-for-using-lazy-containers-in-swiftui/)
-- [List or LazyVStack - Choosing the Right Lazy Container in SwiftUI](https://fatbobman.com/en/posts/list-or-lazyvstack/)
-- [Optimizing AsyncImage in SwiftUI: Build a Custom Cached Solution](https://medium.com/@sviatoslav.kliuchev/improve-asyncimage-in-swiftui-5aae28f1a331)
-- [AsyncImage in SwiftUI: Loading Images from URLs with Caching](https://matteomanferdini.com/swiftui-asyncimage/)
-- [SwiftUI Performance and Stability: Avoiding the Most Costly Mistakes](https://dev.to/arshtechpro/swiftui-performance-and-stability-avoiding-the-most-costly-mistakes-234c)
-- [Common SwiftUI Mistakes - Hacking with Swift](https://www.hackingwithswift.com/articles/224/common-swiftui-mistakes-and-how-to-fix-them)
-- [Avoiding having to recompute values within SwiftUI views - Swift by Sundell](https://www.swiftbysundell.com/articles/avoiding-swiftui-value-recomputation/)
-- [Optimizing SwiftUI: Reducing Body Recalculation and Minimizing @State Updates](https://medium.com/@wesleymatlock/optimizing-swiftui-reducing-body-recalculation-and-minimizing-state-updates-8f7944253725)
-- [How to Avoid Repeating SwiftUI View Updates](https://fatbobman.com/en/posts/avoid_repeated_calculations_of_swiftui_views/)
diff --git a/src/resources/skills/swiftui/references/platform-integration.md b/src/resources/skills/swiftui/references/platform-integration.md
deleted file mode 100644
index 47ec2ad20..000000000
--- a/src/resources/skills/swiftui/references/platform-integration.md
+++ /dev/null
@@ -1,204 +0,0 @@
-
-SwiftUI enables true multiplatform development: write once, adapt per platform. A single codebase can target iOS, iPadOS, macOS, watchOS, tvOS, and visionOS while respecting each platform's unique conventions and capabilities.
-
-**Key insight:** SwiftUI's declarative syntax works everywhere, but each platform has distinct interaction models. iOS uses touch and gestures, macOS has precise mouse input and keyboard shortcuts, watchOS centers on the Digital Crown, and visionOS introduces spatial computing with gaze and hand tracking.
-
-**When to read this:**
-- Building multiplatform apps with shared logic but platform-specific UI
-- Implementing macOS menu bar utilities or Settings windows
-- Creating watchOS complications or Digital Crown interactions
-- Developing visionOS apps with immersive spaces and ornaments
-- Adapting layouts responsively across iPhone, iPad, and Mac
-
-
-
-## Platform Conditionals
-
-**Compile-time platform checks:**
-```swift
-#if os(iOS)
-// iOS-only code
-#elseif os(macOS)
-// macOS-only code
-#elseif os(watchOS)
-// watchOS-only code
-#elseif os(visionOS)
-// visionOS-only code
-#endif
-```
-
-**Runtime API availability:**
-```swift
-if #available(iOS 17, macOS 14, *) {
- // Use iOS 17+/macOS 14+ API
-}
-```
-
-**Target environment:**
-```swift
-#if targetEnvironment(simulator)
-// Running in simulator
-#endif
-
-#if canImport(UIKit)
-// UIKit available
-#endif
-```
-
-
-
-## iOS-Specific Features
-
-**Navigation patterns:**
-- Tab bar at bottom
-- Full-screen covers
-- Pull-to-refresh with .refreshable
-
-**System integration:**
-- Push notifications
-- Widgets and Live Activities
-- App Intents / Siri
-
-**Device variations:**
-```swift
-@Environment(\.horizontalSizeClass) var horizontalSizeClass
-
-if horizontalSizeClass == .regular {
- // iPad layout
-}
-```
-
-
-
-## macOS-Specific Features
-
-**Window management:**
-```swift
-WindowGroup("Main") { ContentView() }
- .defaultSize(width: 800, height: 600)
-
-Window("Settings") { SettingsView() }
-
-Settings { SettingsView() }
-```
-
-**MenuBarExtra:**
-```swift
-MenuBarExtra("App Name", systemImage: "star") {
- MenuBarContentView()
-}
-.menuBarExtraStyle(.window)
-```
-
-**Commands:**
-```swift
-.commands {
- CommandGroup(replacing: .newItem) {
- Button("New Document") { }
- }
- CommandMenu("Custom") {
- Button("Action") { }
- }
-}
-```
-
-
-
-## watchOS-Specific Features
-
-**Digital Crown:**
-```swift
-@State private var crownValue: Double = 0.0
-
-VStack { Text("\(crownValue)") }
- .focusable()
- .digitalCrownRotation($crownValue)
-```
-
-**Always-on display:**
-```swift
-@Environment(\.isLuminanceReduced) var isLuminanceReduced
-```
-
-
-
-## visionOS-Specific Features
-
-**Immersive spaces:**
-```swift
-ImmersiveSpace(id: "immersive") {
- RealityView { content in
- // 3D content
- }
-}
-```
-
-**Window styles:**
-```swift
-.windowStyle(.volumetric)
-```
-
-**Ornaments:**
-```swift
-.ornament(attachmentAnchor: .scene(.bottom)) {
- BottomControls()
-}
-```
-
-
-
-## Responsive Design
-
-**Size classes:**
-```swift
-@Environment(\.horizontalSizeClass) var horizontalSizeClass
-@Environment(\.verticalSizeClass) var verticalSizeClass
-```
-
-**ViewThatFits (iOS 16+):**
-```swift
-ViewThatFits {
- WideLayout()
- CompactLayout()
-}
-```
-
-**containerRelativeFrame (iOS 17+):**
-```swift
-.containerRelativeFrame(.horizontal) { length, axis in
- length * 0.8
-}
-```
-
-
-
-## Platform Strategy
-
-**Shared codebase structure:**
-- Models, ViewModels, Services: All platforms
-- Views: Platform-specific where needed
-
-**When to use conditionals:**
-- Platform-exclusive APIs
-- Different navigation patterns
-- Different default sizes
-
-
-
-## What NOT to Do
-
-
-**Problem:** Platform checks everywhere
-**Instead:** Extract to platform-specific files
-
-
-
-**Problem:** iOS patterns on macOS
-**Instead:** Respect each platform's conventions
-
-
-
-**Problem:** Missing real device behaviors
-**Instead:** Test on physical devices
-
-
diff --git a/src/resources/skills/swiftui/references/state-management.md b/src/resources/skills/swiftui/references/state-management.md
deleted file mode 100644
index f355dc11f..000000000
--- a/src/resources/skills/swiftui/references/state-management.md
+++ /dev/null
@@ -1,1443 +0,0 @@
-
-SwiftUI state management is fundamentally different from imperative UI frameworks. You describe what the UI should look like for any given state, and SwiftUI handles the updates when state changes.
-
-**Read this file when:** Building views that need to respond to data changes, sharing data between views, choosing property wrappers, debugging state issues, or migrating from ObservableObject patterns.
-
-**Key insight:** SwiftUI uses a declarative, unidirectional data flow. State flows down through the view hierarchy via properties. Changes flow up through bindings or actions. You describe state, not mutations.
-
-**Modern SwiftUI (iOS 17+)** uses the @Observable macro for reference types, eliminating most needs for ObservableObject, @Published, @StateObject, @ObservedObject, and @EnvironmentObject. The mental model is simpler: value types use @State/@Binding, reference types use @Observable with @State/@Bindable.
-
-
-
-## Property Wrappers
-
-
-**Purpose:** Manage mutable value types owned by a single view. The source of truth for simple, view-local data.
-
-**When to use:** Simple values (Int, String, Bool, Array, struct) that belong to this view and need to trigger UI updates when changed.
-
-**Ownership:** The view owns this data. SwiftUI manages its lifecycle.
-
-**Lifecycle:** Persists across view body recomputes. Reset when the view is removed from the hierarchy and recreated with a new identity.
-
-```swift
-struct CounterView: View {
- @State private var count = 0
- @State private var isExpanded = false
-
- var body: some View {
- VStack {
- Text("Count: \(count)")
-
- Button("Increment") {
- count += 1
- }
-
- if isExpanded {
- Text("Details about count...")
- }
-
- Toggle("Show Details", isOn: $isExpanded)
- }
- }
-}
-```
-
-**With @Observable classes (iOS 17+):**
-```swift
-@Observable
-class ViewModel {
- var items: [String] = []
- var selectedItem: String?
-}
-
-struct ContentView: View {
- @State private var viewModel = ViewModel()
-
- var body: some View {
- List(viewModel.items, id: \.self) { item in
- Text(item)
- }
- }
-}
-```
-
-**Common mistakes:**
-- Making @State public (should be private to enforce view-local ownership)
-- Using @State for data passed from a parent (use @Binding or receive as plain property)
-- Not initializing @State with a value
-- Using @State with ObservableObject classes pre-iOS 17 (use @StateObject instead)
-
-
-
-**Purpose:** Create a two-way connection to state owned by another view. Allows a child view to read and write a parent's state without owning it.
-
-**When to use:** Passing writable access to value type data from parent to child. The child needs to modify data it doesn't own.
-
-**Ownership:** The parent owns the data. This view has read-write access via reference.
-
-**Lifecycle:** Tied to the source of truth it references.
-
-```swift
-struct ParentView: View {
- @State private var username = ""
-
- var body: some View {
- VStack {
- Text("Hello, \(username)")
- UsernameField(username: $username)
- }
- }
-}
-
-struct UsernameField: View {
- @Binding var username: String
-
- var body: some View {
- TextField("Enter name", text: $username)
- .textFieldStyle(.roundedBorder)
- .padding()
- }
-}
-```
-
-**With custom controls:**
-```swift
-struct ToggleButton: View {
- @Binding var isOn: Bool
- let label: String
-
- var body: some View {
- Button(label) {
- isOn.toggle()
- }
- .foregroundStyle(isOn ? .green : .gray)
- }
-}
-
-// Usage
-struct ContentView: View {
- @State private var notificationsEnabled = false
-
- var body: some View {
- ToggleButton(
- isOn: $notificationsEnabled,
- label: "Notifications"
- )
- }
-}
-```
-
-**Common mistakes:**
-- Providing a default value to @Binding (bindings are always passed from outside)
-- Making @Binding private (it must be accessible to receive the binding)
-- Passing the value without $ prefix (passes a copy, not a binding)
-- Using @Binding when the child shouldn't modify the value (use a plain property instead)
-
-
-
-**Purpose:** Mark a class as observable so SwiftUI automatically tracks property changes and updates views. Replaces ObservableObject protocol in iOS 17+.
-
-**When to use:** Reference type data models shared across multiple views. Complex state that benefits from reference semantics. Data that needs to be passed down the view hierarchy.
-
-**Ownership:** Created and owned by a view using @State, or passed through @Environment for app-wide access.
-
-**Lifecycle:** Follows standard Swift reference type lifecycle. When stored in @State, survives view body recomputes.
-
-```swift
-import Observation
-
-@Observable
-class ShoppingCart {
- var items: [Item] = []
- var discount: Double = 0.0
-
- var total: Double {
- let subtotal = items.reduce(0) { $0 + $1.price }
- return subtotal * (1 - discount)
- }
-
- func addItem(_ item: Item) {
- items.append(item)
- }
-}
-
-struct StoreView: View {
- @State private var cart = ShoppingCart()
-
- var body: some View {
- VStack {
- CartSummary(cart: cart)
- ProductList(cart: cart)
- }
- }
-}
-
-struct CartSummary: View {
- var cart: ShoppingCart // Plain property, no wrapper needed
-
- var body: some View {
- Text("Total: $\(cart.total, specifier: "%.2f")")
- .font(.headline)
- }
-}
-```
-
-**With @ObservationIgnored for non-tracked properties:**
-```swift
-@Observable
-class UserSession {
- var username: String = ""
- var loginCount: Int = 0
-
- @ObservationIgnored
- var temporaryCache: [String: Any] = [:] // Won't trigger view updates
-}
-```
-
-**Common mistakes:**
-- Using @Published with @Observable (not needed, all properties are observed by default)
-- Forgetting to import Observation
-- Using @StateObject instead of @State for @Observable classes
-- Not using @ObservationIgnored for properties that shouldn't trigger updates (like caches, formatters)
-
-
-
-**Purpose:** Create bindings to properties of @Observable objects when the view doesn't own the object. Bridges @Observable with SwiftUI's $ binding syntax.
-
-**When to use:** You have an @Observable object passed from a parent, and you need to create two-way bindings to its properties (for TextField, Toggle, etc.).
-
-**Ownership:** The view doesn't own the object. It's passed from outside.
-
-**Lifecycle:** Tied to the lifecycle of the @Observable object it references.
-
-```swift
-@Observable
-class FormData {
- var name: String = ""
- var email: String = ""
- var agreedToTerms: Bool = false
-}
-
-struct ParentView: View {
- @State private var formData = FormData()
-
- var body: some View {
- FormView(formData: formData)
- }
-}
-
-struct FormView: View {
- @Bindable var formData: FormData
-
- var body: some View {
- Form {
- TextField("Name", text: $formData.name)
- TextField("Email", text: $formData.email)
- Toggle("I agree to terms", isOn: $formData.agreedToTerms)
- }
- }
-}
-```
-
-**Nested child views:**
-```swift
-struct NestedChildView: View {
- @Bindable var formData: FormData
-
- var body: some View {
- // Can still create bindings to properties
- Toggle("Marketing emails", isOn: $formData.agreedToTerms)
- }
-}
-```
-
-**Common mistakes:**
-- Using @Bindable when you own the object (use @State instead)
-- Using @Binding for @Observable objects (use @Bindable for reference types)
-- Forgetting that @Bindable doesn't work with ObservableObject (legacy pattern)
-- Using @ObservedObject instead of @Bindable for iOS 17+ code
-
-
-
-**Purpose:** Read values from SwiftUI's environment or inject custom values accessible throughout the view hierarchy. Replaces @EnvironmentObject in iOS 17+.
-
-**When to use:**
-- Accessing system values (colorScheme, locale, dismiss, etc.)
-- Sharing app-wide or subtree-wide state without prop drilling
-- Dependency injection for services and models
-
-**Ownership:** Provided by ancestor views or the system. Current view reads it.
-
-**Lifecycle:** Managed by the provider. Available to all descendant views.
-
-```swift
-// System environment values
-struct ThemedView: View {
- @Environment(\.colorScheme) var colorScheme
- @Environment(\.dismiss) var dismiss
-
- var body: some View {
- VStack {
- Text("Current theme: \(colorScheme == .dark ? "Dark" : "Light")")
-
- Button("Close") {
- dismiss()
- }
- }
- .foregroundStyle(colorScheme == .dark ? .white : .black)
- }
-}
-```
-
-**Custom environment values (iOS 17+):**
-```swift
-@Observable
-class AppSettings {
- var fontSize: Double = 16
- var accentColor: Color = .blue
-}
-
-// In your app root
-@main
-struct MyApp: App {
- @State private var settings = AppSettings()
-
- var body: some Scene {
- WindowGroup {
- ContentView()
- .environment(settings)
- }
- }
-}
-
-// Access in any descendant view
-struct SettingsView: View {
- @Environment(AppSettings.self) var settings
-
- var body: some View {
- VStack {
- Text("Font size: \(settings.fontSize)")
- ColorPicker("Accent", selection: $settings.accentColor)
- }
- }
-}
-```
-
-**Legacy custom environment values (pre-iOS 17):**
-```swift
-private struct ThemeKey: EnvironmentKey {
- static let defaultValue = Theme.light
-}
-
-extension EnvironmentValues {
- var theme: Theme {
- get { self[ThemeKey.self] }
- set { self[ThemeKey.self] = newValue }
- }
-}
-
-// Usage
-struct ContentView: View {
- @Environment(\.theme) var theme
-
- var body: some View {
- Text("Hello")
- .foregroundStyle(theme.textColor)
- }
-}
-```
-
-**Common mistakes:**
-- Using @EnvironmentObject instead of @Environment for iOS 17+ code
-- Not providing the environment value before accessing it (runtime crash)
-- Overusing environment for data that should be passed as properties
-- Using environment for frequently changing values (can cause unnecessary updates)
-
-
-
-**Purpose:** Read and write UserDefaults values with automatic UI updates when the value changes.
-
-**When to use:** Storing user preferences, settings, or small amounts of persistent data that should survive app relaunches.
-
-**Ownership:** Backed by UserDefaults. View has read-write access.
-
-**Lifecycle:** Persists between app launches until explicitly removed.
-
-```swift
-struct SettingsView: View {
- @AppStorage("username") private var username = "Guest"
- @AppStorage("notificationsEnabled") private var notificationsEnabled = true
- @AppStorage("theme") private var theme = "system"
-
- var body: some View {
- Form {
- TextField("Username", text: $username)
-
- Toggle("Notifications", isOn: $notificationsEnabled)
-
- Picker("Theme", selection: $theme) {
- Text("System").tag("system")
- Text("Light").tag("light")
- Text("Dark").tag("dark")
- }
- }
- }
-}
-```
-
-**With custom UserDefaults suite:**
-```swift
-struct SharedSettingsView: View {
- @AppStorage("syncEnabled", store: UserDefaults(suiteName: "group.com.example.app"))
- private var syncEnabled = false
-
- var body: some View {
- Toggle("Sync", isOn: $syncEnabled)
- }
-}
-```
-
-**Supported types:** Bool, Int, Double, String, URL, Data
-
-**Common mistakes:**
-- Storing sensitive data (UserDefaults is not encrypted)
-- Storing large amounts of data (performance degradation)
-- Using for data that changes frequently during a session (use @State instead)
-- Not providing a default value
-- Assuming cross-app synchronization (requires App Groups configuration)
-
-
-
-**Purpose:** Automatic state restoration per scene. Saves and restores values when the app is backgrounded/foregrounded or scenes are destroyed/recreated.
-
-**When to use:** Preserving UI state for state restoration (selected tab, scroll position, current navigation path, form data).
-
-**Ownership:** Managed per scene by the system. View has read-write access.
-
-**Lifecycle:** Persists when app backgrounds. Destroyed when user explicitly kills the app from the app switcher.
-
-```swift
-struct ContentView: View {
- @SceneStorage("selectedTab") private var selectedTab = "home"
-
- var body: some View {
- TabView(selection: $selectedTab) {
- HomeView()
- .tabItem { Label("Home", systemImage: "house") }
- .tag("home")
-
- ProfileView()
- .tabItem { Label("Profile", systemImage: "person") }
- .tag("profile")
- }
- }
-}
-```
-
-**With navigation state:**
-```swift
-struct NavigationExample: View {
- @SceneStorage("navigationPath") private var navigationPathData: Data?
- @State private var path = NavigationPath()
-
- var body: some View {
- NavigationStack(path: $path) {
- List {
- NavigationLink("Details", value: "details")
- }
- .navigationDestination(for: String.self) { value in
- Text("Showing: \(value)")
- }
- }
- .onAppear {
- if let data = navigationPathData,
- let restored = try? JSONDecoder().decode(NavigationPath.CodableRepresentation.self, from: data) {
- path = NavigationPath(restored)
- }
- }
- .onChange(of: path) { _, newPath in
- if let representation = newPath.codable,
- let data = try? JSONEncoder().encode(representation) {
- navigationPathData = data
- }
- }
- }
-}
-```
-
-**Supported types:** Bool, Int, Double, String, URL, Data
-
-**Common mistakes:**
-- Storing sensitive data (not secure)
-- Storing large amounts of data (Apple warns against this)
-- Expecting data to persist after force-quit (it's cleared)
-- Using for cross-scene data (each scene has its own storage)
-- Not providing a default value
-
-
-
-**Purpose:** Create and own an ObservableObject in a view. Ensures the object survives view body recomputes.
-
-**When to use:** Legacy code (pre-iOS 17) when you need to create and own an ObservableObject. For iOS 17+, use @State with @Observable instead.
-
-**Ownership:** The view owns and manages the object's lifecycle.
-
-**Lifecycle:** Created once when the view is initialized. Survives view body recomputes. Destroyed when view is removed.
-
-```swift
-// Legacy pattern (pre-iOS 17)
-class LegacyViewModel: ObservableObject {
- @Published var count = 0
- @Published var items: [String] = []
-}
-
-struct LegacyView: View {
- @StateObject private var viewModel = LegacyViewModel()
-
- var body: some View {
- VStack {
- Text("Count: \(viewModel.count)")
-
- Button("Increment") {
- viewModel.count += 1
- }
- }
- }
-}
-```
-
-**Common mistakes:**
-- Using @StateObject for iOS 17+ (use @State with @Observable instead)
-- Using @ObservedObject when the view creates the object (causes recreation bugs)
-- Creating @StateObject in non-root views unnecessarily (consider passing from parent)
-- Using for value types (use @State instead)
-
-
-
-**Purpose:** Observe an ObservableObject owned by another view. Doesn't create or own the object.
-
-**When to use:** Legacy code (pre-iOS 17) when receiving an ObservableObject from a parent. For iOS 17+, pass @Observable objects as plain properties.
-
-**Ownership:** Parent or external source owns the object. This view observes it.
-
-**Lifecycle:** Tied to the source that owns it.
-
-```swift
-// Legacy pattern (pre-iOS 17)
-class SharedViewModel: ObservableObject {
- @Published var data: String = ""
-}
-
-struct ParentView: View {
- @StateObject private var viewModel = SharedViewModel()
-
- var body: some View {
- ChildView(viewModel: viewModel)
- }
-}
-
-struct ChildView: View {
- @ObservedObject var viewModel: SharedViewModel
-
- var body: some View {
- Text(viewModel.data)
- }
-}
-```
-
-**Common mistakes:**
-- Creating the object within the view using @ObservedObject (use @StateObject instead, or @State for @Observable)
-- Using for iOS 17+ code (pass @Observable objects as plain properties)
-- Confusing ownership (if you create it, you own it - use @StateObject not @ObservedObject)
-
-
-
-
-## Choosing the Right Property Wrapper
-
-**iOS 17+ Decision Process:**
-
-1. **Is this a value type (Int, String, Bool, struct)?**
- - Owned by this view? → `@State`
- - Passed from parent, needs modification? → `@Binding`
- - Just reading it? → Plain property
-
-2. **Is this an @Observable class?**
- - Created and owned by this view? → `@State`
- - Passed from parent, need to create bindings to properties? → `@Bindable`
- - Passed from parent, just reading? → Plain property
- - App-wide or subtree-wide access? → `@Environment`
-
-3. **Is this a system value or custom environment value?**
- → `@Environment`
-
-4. **Does this need to persist to UserDefaults?**
- → `@AppStorage`
-
-5. **Does this need automatic state restoration per scene?**
- → `@SceneStorage`
-
-**Pre-iOS 17 Decision Process:**
-
-1. **Is this a value type?**
- - Owned by this view? → `@State`
- - Passed from parent? → `@Binding`
-
-2. **Is this an ObservableObject?**
- - Created by this view? → `@StateObject`
- - Passed from parent? → `@ObservedObject`
- - App-wide access? → `@EnvironmentObject`
-
-3. **Is this a system value?**
- → `@Environment`
-
-**Quick Reference Table:**
-
-| Data Type | Ownership | iOS 17+ | Pre-iOS 17 |
-|-----------|-----------|---------|------------|
-| Value type | Own | @State | @State |
-| Value type | Parent owns, need write | @Binding | @Binding |
-| Value type | Parent owns, read only | Plain property | Plain property |
-| @Observable class | Own | @State | N/A |
-| @Observable class | Parent owns, need bindings | @Bindable | N/A |
-| @Observable class | Parent owns, read only | Plain property | N/A |
-| @Observable class | App-wide | @Environment | N/A |
-| ObservableObject | Own | N/A | @StateObject |
-| ObservableObject | Parent owns | N/A | @ObservedObject |
-| ObservableObject | App-wide | N/A | @EnvironmentObject |
-| System values | N/A | @Environment | @Environment |
-| UserDefaults | N/A | @AppStorage | @AppStorage |
-| State restoration | N/A | @SceneStorage | @SceneStorage |
-
-
-
-## Common Patterns
-
-
-**Use when:** Building any SwiftUI view hierarchy. This is the fundamental pattern.
-
-**Concept:** Data flows down the view hierarchy as properties. Changes flow up through bindings or callbacks. State has a single source of truth.
-
-**Implementation:**
-```swift
-@Observable
-class AppState {
- var items: [Item] = []
- var selectedItemId: UUID?
-
- func selectItem(_ id: UUID) {
- selectedItemId = id
- }
-
- func addItem(_ item: Item) {
- items.append(item)
- }
-}
-
-struct AppView: View {
- @State private var appState = AppState()
-
- var body: some View {
- NavigationStack {
- ItemList(
- items: appState.items,
- selectedId: appState.selectedItemId,
- onSelect: { appState.selectItem($0) }
- )
- }
- }
-}
-
-struct ItemList: View {
- let items: [Item]
- let selectedId: UUID?
- let onSelect: (UUID) -> Void
-
- var body: some View {
- List(items) { item in
- ItemRow(
- item: item,
- isSelected: item.id == selectedId,
- onTap: { onSelect(item.id) }
- )
- }
- }
-}
-
-struct ItemRow: View {
- let item: Item
- let isSelected: Bool
- let onTap: () -> Void
-
- var body: some View {
- HStack {
- Text(item.name)
- if isSelected {
- Image(systemName: "checkmark")
- }
- }
- .onTapGesture(perform: onTap)
- }
-}
-```
-
-**Considerations:**
-- Clear data flow is easier to debug than bidirectional mutations
-- Callbacks can become verbose for deeply nested hierarchies (consider @Environment)
-- Single source of truth prevents sync issues
-
-
-
-**Use when:** Multiple views need access to shared state without prop drilling. Dependency injection for services.
-
-**Implementation:**
-```swift
-@Observable
-class UserSession {
- var isLoggedIn = false
- var username: String?
-
- func login(username: String) {
- self.username = username
- isLoggedIn = true
- }
-
- func logout() {
- username = nil
- isLoggedIn = false
- }
-}
-
-@main
-struct MyApp: App {
- @State private var session = UserSession()
-
- var body: some Scene {
- WindowGroup {
- ContentView()
- .environment(session)
- }
- }
-}
-
-struct ContentView: View {
- @Environment(UserSession.self) private var session
-
- var body: some View {
- if session.isLoggedIn {
- HomeView()
- } else {
- LoginView()
- }
- }
-}
-
-struct LoginView: View {
- @Environment(UserSession.self) private var session
- @State private var username = ""
-
- var body: some View {
- VStack {
- TextField("Username", text: $username)
- Button("Login") {
- session.login(username: username)
- }
- }
- }
-}
-
-struct HomeView: View {
- @Environment(UserSession.self) private var session
-
- var body: some View {
- VStack {
- Text("Welcome, \(session.username ?? "")")
- Button("Logout") {
- session.logout()
- }
- }
- }
-}
-```
-
-**Considerations:**
-- Convenient for app-wide state (settings, auth, theme)
-- Runtime crash if environment value not provided
-- Can make testing harder (need to provide environment in previews/tests)
-- Overuse can hide dependencies and make data flow unclear
-
-
-
-**Use when:** Computing values from other state. Avoid storing redundant state.
-
-**Implementation:**
-```swift
-@Observable
-class ShoppingCart {
- var items: [CartItem] = []
- var discountCode: String?
-
- // Derived - computed from items
- var subtotal: Double {
- items.reduce(0) { $0 + ($1.price * Double($1.quantity)) }
- }
-
- // Derived - computed from subtotal and discountCode
- var discount: Double {
- guard let code = discountCode else { return 0 }
- switch code {
- case "SAVE10": return subtotal * 0.1
- case "SAVE20": return subtotal * 0.2
- default: return 0
- }
- }
-
- // Derived - computed from subtotal and discount
- var total: Double {
- subtotal - discount
- }
-}
-
-struct CartView: View {
- @State private var cart = ShoppingCart()
-
- var body: some View {
- VStack {
- List(cart.items) { item in
- HStack {
- Text(item.name)
- Spacer()
- Text("$\(item.price * Double(item.quantity), specifier: "%.2f")")
- }
- }
-
- Divider()
-
- HStack {
- Text("Subtotal:")
- Spacer()
- Text("$\(cart.subtotal, specifier: "%.2f")")
- }
-
- if cart.discount > 0 {
- HStack {
- Text("Discount:")
- Spacer()
- Text("-$\(cart.discount, specifier: "%.2f")")
- .foregroundStyle(.green)
- }
- }
-
- HStack {
- Text("Total:")
- .bold()
- Spacer()
- Text("$\(cart.total, specifier: "%.2f")")
- .bold()
- }
- }
- }
-}
-```
-
-**Considerations:**
-- Computed properties are always in sync with source data
-- No need to manually update derived state
-- Recomputed on every access (cache if expensive)
-- Keep computations simple or consider caching
-
-
-
-**Use when:** State only matters for presentation, not business logic (UI-only state like selection, expansion, animation).
-
-**Implementation:**
-```swift
-struct ExpandableCard: View {
- let content: String
- @State private var isExpanded = false // UI state only
-
- var body: some View {
- VStack(alignment: .leading) {
- HStack {
- Text(content.prefix(50))
- .lineLimit(isExpanded ? nil : 1)
- Spacer()
- Image(systemName: isExpanded ? "chevron.up" : "chevron.down")
- }
-
- if isExpanded {
- Text(content)
- .font(.caption)
- .foregroundStyle(.secondary)
- }
- }
- .padding()
- .background(.quaternary)
- .cornerRadius(8)
- .onTapGesture {
- withAnimation {
- isExpanded.toggle()
- }
- }
- }
-}
-```
-
-**Considerations:**
-- Keeps business logic separate from UI state
-- Resets naturally when view is recreated
-- Makes components self-contained and reusable
-- Consider if state needs to persist (use @SceneStorage for restoration)
-
-
-
-**Use when:** Two sibling views need to share mutable state.
-
-**Implementation:**
-```swift
-struct ParentView: View {
- @State private var searchQuery = "" // Shared state lives in parent
-
- var body: some View {
- VStack {
- SearchBar(query: $searchQuery) // Pass binding to both siblings
- SearchResults(query: searchQuery)
- }
- }
-}
-
-struct SearchBar: View {
- @Binding var query: String
-
- var body: some View {
- TextField("Search", text: $query)
- .textFieldStyle(.roundedBorder)
- .padding()
- }
-}
-
-struct SearchResults: View {
- let query: String
-
- var body: some View {
- List {
- // Filter results based on query
- Text("Results for: \(query)")
- }
- }
-}
-```
-
-**Considerations:**
-- State lives in lowest common ancestor
-- Clear data flow (parent owns, children use)
-- Siblings can't directly communicate (goes through parent)
-- Consider @Observable model if state becomes complex
-
-
-
-
-## What NOT to Do
-
-
-**Problem:** Creating an ObservableObject with @ObservedObject instead of @StateObject.
-
-**Why it's bad:** SwiftUI can recreate views at any time. @ObservedObject doesn't guarantee the object survives, causing data loss, crashes, and unpredictable behavior. The object gets recreated on every view update.
-
-**Instead:**
-```swift
-// WRONG
-struct MyView: View {
- @ObservedObject var viewModel = ViewModel() // ❌ Will be recreated!
- var body: some View { /* ... */ }
-}
-
-// RIGHT (pre-iOS 17)
-struct MyView: View {
- @StateObject private var viewModel = ViewModel() // ✅ Survives redraws
- var body: some View { /* ... */ }
-}
-
-// RIGHT (iOS 17+)
-@Observable
-class ViewModel {
- var data = ""
-}
-
-struct MyView: View {
- @State private var viewModel = ViewModel() // ✅ Modern approach
- var body: some View { /* ... */ }
-}
-```
-
-
-
-**Problem:** Declaring @State properties as public or internal.
-
-**Why it's bad:** @State is meant for view-local state. Making it public violates encapsulation and suggests the state should be passed from outside (making it not truly @State). Creates confusion about ownership.
-
-**Instead:**
-```swift
-// WRONG
-struct MyView: View {
- @State var count = 0 // ❌ Not private
- var body: some View { /* ... */ }
-}
-
-// RIGHT
-struct MyView: View {
- @State private var count = 0 // ✅ Private ownership
- var body: some View { /* ... */ }
-}
-
-// If state needs to come from outside:
-struct MyView: View {
- @Binding var count: Int // ✅ Use @Binding instead
- var body: some View { /* ... */ }
-}
-```
-
-
-
-**Problem:** Storing large value types or arrays in @State, causing performance issues.
-
-**Why it's bad:** SwiftUI recreates the view body whenever @State changes. Large value types cause expensive copies. Massive arrays cause performance degradation.
-
-**Instead:**
-```swift
-// WRONG
-struct ListView: View {
- @State private var items: [LargeItem] = loadThousandsOfItems() // ❌ Expensive copies
- var body: some View { /* ... */ }
-}
-
-// RIGHT
-@Observable
-class ItemStore {
- var items: [LargeItem] = [] // Reference type, no copies
-}
-
-struct ListView: View {
- @State private var store = ItemStore() // ✅ Only reference is copied
- var body: some View { /* ... */ }
-}
-```
-
-
-
-**Problem:** Passing a state value to a child expecting @Binding without the $ prefix.
-
-**Why it's bad:** Passes a copy of the value instead of a binding. Child's changes don't propagate back to parent.
-
-**Instead:**
-```swift
-struct ParentView: View {
- @State private var text = ""
-
- var body: some View {
- // WRONG
- ChildView(text: text) // ❌ Passes copy
-
- // RIGHT
- ChildView(text: $text) // ✅ Passes binding
- }
-}
-
-struct ChildView: View {
- @Binding var text: String
- var body: some View {
- TextField("Enter text", text: $text)
- }
-}
-```
-
-
-
-**Problem:** Changing @State or @Observable properties inside computed properties or body.
-
-**Why it's bad:** Causes infinite loops or unpredictable update cycles. SwiftUI reads body to determine what to render; mutating state during rendering triggers another render.
-
-**Instead:**
-```swift
-// WRONG
-struct MyView: View {
- @State private var count = 0
-
- var body: some View {
- let _ = count += 1 // ❌ Infinite loop!
- Text("Count: \(count)")
- }
-}
-
-// RIGHT
-struct MyView: View {
- @State private var count = 0
-
- var body: some View {
- VStack {
- Text("Count: \(count)")
- Button("Increment") {
- count += 1 // ✅ Mutate in response to events
- }
- }
- .onAppear {
- count = 0 // ✅ Or in lifecycle events
- }
- }
-}
-```
-
-
-
-**Problem:** Using @AppStorage for passwords, tokens, or other sensitive data.
-
-**Why it's bad:** UserDefaults is not encrypted. Data is easily accessible to anyone with device access or backup access. Security vulnerability.
-
-**Instead:**
-```swift
-// WRONG
-@AppStorage("password") private var password = "" // ❌ Not secure!
-@AppStorage("authToken") private var token = "" // ❌ Not secure!
-
-// RIGHT
-import Security
-
-class KeychainManager {
- func save(password: String, for account: String) {
- // Use Keychain for sensitive data
- let query: [String: Any] = [
- kSecClass as String: kSecClassGenericPassword,
- kSecAttrAccount as String: account,
- kSecValueData as String: password.data(using: .utf8)!
- ]
- SecItemAdd(query as CFDictionary, nil)
- }
-}
-
-// For auth tokens, user credentials, etc.
-struct SecureView: View {
- @State private var keychain = KeychainManager()
-
- var body: some View {
- Button("Save Password") {
- keychain.save(password: "secret", for: "user@example.com")
- }
- }
-}
-```
-
-
-
-**Problem:** Using ObservableObject, @Published, @StateObject, @ObservedObject, @EnvironmentObject in new iOS 17+ projects.
-
-**Why it's bad:** The @Observable macro is simpler, more performant, and the recommended approach. Legacy patterns add unnecessary complexity. Better compiler optimization with @Observable.
-
-**Instead:**
-```swift
-// WRONG (legacy)
-class ViewModel: ObservableObject {
- @Published var name = ""
- @Published var count = 0
-}
-
-struct OldView: View {
- @StateObject private var viewModel = ViewModel()
- var body: some View { /* ... */ }
-}
-
-// RIGHT (iOS 17+)
-@Observable
-class ViewModel {
- var name = ""
- var count = 0
-}
-
-struct ModernView: View {
- @State private var viewModel = ViewModel()
- var body: some View { /* ... */ }
-}
-```
-
-
-
-**Problem:** Putting everything in @Environment, even data that should be passed as properties.
-
-**Why it's bad:** Hides dependencies, makes views harder to test and preview, unclear data flow, runtime crashes if environment not provided.
-
-**Instead:**
-```swift
-// WRONG - overusing environment
-struct ItemRow: View {
- @Environment(AppState.self) private var appState // ❌ Just to access one property
-
- var body: some View {
- Text(appState.currentItem.name)
- }
-}
-
-// RIGHT - explicit dependencies
-struct ItemRow: View {
- let item: Item // ✅ Clear dependency
-
- var body: some View {
- Text(item.name)
- }
-}
-
-// Environment is good for truly cross-cutting concerns:
-struct ThemedView: View {
- @Environment(\.colorScheme) var colorScheme // ✅ System value
- @Environment(UserSession.self) var session // ✅ App-wide auth state
-
- var body: some View { /* ... */ }
-}
-```
-
-
-
-
-## Migrating from Legacy Patterns
-
-**ObservableObject → @Observable:**
-
-```swift
-// Before (legacy)
-class ViewModel: ObservableObject {
- @Published var name: String = ""
- @Published var count: Int = 0
- @Published var items: [Item] = []
-
- private var cache: [String: Any] = [:] // Not published
-}
-
-struct OldView: View {
- @StateObject private var viewModel = ViewModel()
-
- var body: some View {
- Text(viewModel.name)
- }
-}
-
-// After (iOS 17+)
-import Observation
-
-@Observable
-class ViewModel {
- var name: String = ""
- var count: Int = 0
- var items: [Item] = []
-
- @ObservationIgnored
- private var cache: [String: Any] = [:] // Won't trigger updates
-}
-
-struct ModernView: View {
- @State private var viewModel = ViewModel()
-
- var body: some View {
- Text(viewModel.name)
- }
-}
-```
-
-**@EnvironmentObject → @Environment:**
-
-```swift
-// Before (legacy)
-class AppSettings: ObservableObject {
- @Published var theme: String = "light"
-}
-
-@main
-struct OldApp: App {
- @StateObject private var settings = AppSettings()
-
- var body: some Scene {
- WindowGroup {
- ContentView()
- .environmentObject(settings)
- }
- }
-}
-
-struct OldContentView: View {
- @EnvironmentObject var settings: AppSettings
-
- var body: some View {
- Text("Theme: \(settings.theme)")
- }
-}
-
-// After (iOS 17+)
-@Observable
-class AppSettings {
- var theme: String = "light"
-}
-
-@main
-struct ModernApp: App {
- @State private var settings = AppSettings()
-
- var body: some Scene {
- WindowGroup {
- ContentView()
- .environment(settings)
- }
- }
-}
-
-struct ModernContentView: View {
- @Environment(AppSettings.self) private var settings
-
- var body: some View {
- Text("Theme: \(settings.theme)")
- }
-}
-```
-
-**@ObservedObject (child views) → Plain properties:**
-
-```swift
-// Before (legacy)
-class SharedData: ObservableObject {
- @Published var value: String = ""
-}
-
-struct ParentView: View {
- @StateObject private var data = SharedData()
-
- var body: some View {
- ChildView(data: data)
- }
-}
-
-struct ChildView: View {
- @ObservedObject var data: SharedData
-
- var body: some View {
- Text(data.value)
- }
-}
-
-// After (iOS 17+)
-@Observable
-class SharedData {
- var value: String = ""
-}
-
-struct ParentView: View {
- @State private var data = SharedData()
-
- var body: some View {
- ChildView(data: data)
- }
-}
-
-struct ChildView: View {
- var data: SharedData // Plain property, no wrapper
-
- var body: some View {
- Text(data.value)
- }
-}
-```
-
-**Creating bindings to @Observable properties:**
-
-```swift
-// Before (legacy)
-class FormData: ObservableObject {
- @Published var username: String = ""
- @Published var email: String = ""
-}
-
-struct LegacyForm: View {
- @ObservedObject var formData: FormData
-
- var body: some View {
- Form {
- TextField("Username", text: $formData.username)
- TextField("Email", text: $formData.email)
- }
- }
-}
-
-// After (iOS 17+)
-@Observable
-class FormData {
- var username: String = ""
- var email: String = ""
-}
-
-struct ModernForm: View {
- @Bindable var formData: FormData
-
- var body: some View {
- Form {
- TextField("Username", text: $formData.username)
- TextField("Email", text: $formData.email)
- }
- }
-}
-```
-
-**Migration checklist:**
-
-1. Add `import Observation` to files using @Observable
-2. Replace `class X: ObservableObject` with `@Observable class X`
-3. Remove `@Published` from properties (all properties are observed by default)
-4. Add `@ObservationIgnored` to properties that shouldn't trigger updates
-5. Replace `@StateObject` with `@State` in owning views
-6. Replace `@ObservedObject` with plain properties in child views (no wrapper)
-7. Replace `@EnvironmentObject` with `@Environment(Type.self)`
-8. Replace `.environmentObject(obj)` with `.environment(obj)`
-9. Use `@Bindable` when you need to create bindings to @Observable properties
-10. Test thoroughly - SwiftUI will warn about missing environment values at runtime
-
-
-
-## Debugging State Issues
-
-**State not updating views:**
-- Verify property is marked with correct wrapper (@State, @Observable)
-- Check that mutations happen on main thread for UI updates
-- Ensure @ObservationIgnored isn't on properties that should update views
-- Confirm view is actually observing the state (proper property wrapper usage)
-
-**Views updating too much:**
-- Check if @Observable class is triggering updates from non-UI properties (use @ObservationIgnored)
-- Verify child views aren't receiving entire model when they only need specific properties
-- Consider breaking large models into smaller focused models
-- Use Instruments Time Profiler to identify expensive body computations
-
-**Runtime crashes:**
-- "Missing @Environment" - Forgot to provide environment value with `.environment(value)`
-- Force unwrapping nil @AppStorage or @SceneStorage - Always provide default values
-- Access to deallocated object - Using @ObservedObject instead of @StateObject for owned objects
-
-**Previews not working:**
-- Provide all required @Environment values in preview
-- Initialize @Binding properties with `.constant(value)` in previews
-- Ensure @Observable classes are properly initialized
-
-**Example debugging view:**
-```swift
-struct DebugStateView: View {
- @State private var viewModel = ViewModel()
-
- var body: some View {
- VStack {
- Text("Count: \(viewModel.count)")
- Button("Increment") {
- print("Before: \(viewModel.count)")
- viewModel.count += 1
- print("After: \(viewModel.count)")
- }
- }
- // Add debugging modifier
- ._printChanges() // Prints when view updates and why
- }
-}
-```
-
diff --git a/src/resources/skills/swiftui/references/swiftdata.md b/src/resources/skills/swiftui/references/swiftdata.md
deleted file mode 100644
index 02ec7fad1..000000000
--- a/src/resources/skills/swiftui/references/swiftdata.md
+++ /dev/null
@@ -1,297 +0,0 @@
-
-SwiftData is Apple's modern persistence framework introduced at WWDC 2023, built on Core Data but with a Swift-native API. It provides declarative data modeling, automatic persistence, and seamless SwiftUI integration with minimal boilerplate.
-
-**Key insight:** SwiftData eliminates the complexity of Core Data while maintaining its power. Where Core Data requires NSManagedObject subclasses, fetch request controllers, and entity descriptions, SwiftData uses Swift macros (@Model, @Query) and modern Swift features like #Predicate for compile-time validation.
-
-**Minimum deployment:** iOS 17, macOS 14, watchOS 10, tvOS 17, visionOS 1.0
-
-**When to read this file:**
-- Persisting app data locally or syncing with iCloud
-- Defining data models and relationships
-- Querying and filtering stored data
-- Migrating from Core Data to SwiftData
-- Before reading: architecture.md (understand app structure), state-management.md (understand @Observable)
-- Read alongside: platform-integration.md (for CloudKit integration details)
-
-
-
-## Defining Models
-
-**@Model macro:**
-```swift
-import SwiftData
-
-@Model
-class Item {
- var name: String
- var timestamp: Date
- var isCompleted: Bool
-
- init(name: String) {
- self.name = name
- self.timestamp = Date()
- self.isCompleted = false
- }
-}
-```
-
-The @Model macro transforms a Swift class into a SwiftData model. SwiftData automatically persists all stored properties.
-
-**Supported property types:**
-- Basic types: String, Int, Double, Bool, Date, UUID, URL, Data
-- Codable types (stored as JSON)
-- Collections: [String], [Int], etc.
-- Relationships to other @Model types
-- Optionals of any above type
-
-**@Attribute options:**
-```swift
-@Model
-class User {
- @Attribute(.unique) var id: UUID
- @Attribute(.externalStorage) var profileImage: Data
- @Attribute(.spotlight) var displayName: String
- @Attribute(.allowsCloudEncryption) var sensitiveInfo: String
-
- var email: String
-
- init(id: UUID = UUID(), displayName: String, email: String) {
- self.id = id
- self.displayName = displayName
- self.email = email
- self.profileImage = Data()
- self.sensitiveInfo = ""
- }
-}
-```
-
-**@Transient for non-persisted properties:**
-```swift
-@Model
-class Task {
- var title: String
- var createdAt: Date
-
- @Transient var isEditing: Bool = false
-
- var ageInDays: Int {
- Calendar.current.dateComponents([.day], from: createdAt, to: Date()).day ?? 0
- }
-
- init(title: String) {
- self.title = title
- self.createdAt = Date()
- }
-}
-```
-
-
-
-## Relationships
-
-**One-to-many:**
-```swift
-@Model
-class Folder {
- var name: String
- @Relationship(deleteRule: .cascade) var items: [Item] = []
-
- init(name: String) {
- self.name = name
- }
-}
-
-@Model
-class Item {
- var name: String
- var folder: Folder?
-
- init(name: String, folder: Folder? = nil) {
- self.name = name
- self.folder = folder
- }
-}
-```
-
-**Delete rules:**
-- `.cascade` - deletes related objects
-- `.nullify` - sets relationship to nil (default)
-- `.deny` - prevents deletion if relationship exists
-- `.noAction` - does nothing (use with caution)
-
-**Inverse relationships:**
-```swift
-@Model
-class Author {
- var name: String
- @Relationship(inverse: \Book.author) var books: [Book] = []
-
- init(name: String) {
- self.name = name
- }
-}
-```
-
-
-
-## ModelContainer and ModelContext
-
-**Setting up container in App:**
-```swift
-import SwiftUI
-import SwiftData
-
-@main
-struct MyApp: App {
- var body: some Scene {
- WindowGroup {
- ContentView()
- }
- .modelContainer(for: [Item.self, Folder.self])
- }
-}
-```
-
-**Custom configuration:**
-```swift
-let config = ModelConfiguration(
- schema: Schema([Item.self, Folder.self]),
- url: URL.documentsDirectory.appending(path: "MyApp.store"),
- cloudKitDatabase: .automatic
-)
-
-let container = try ModelContainer(
- for: Item.self,
- configurations: config
-)
-```
-
-**Accessing context in views:**
-```swift
-@Environment(\.modelContext) private var context
-```
-
-
-
-## Querying Data
-
-**@Query in views:**
-```swift
-@Query var items: [Item]
-
-// With sorting
-@Query(sort: \Item.timestamp, order: .reverse) var items: [Item]
-
-// With filtering
-@Query(filter: #Predicate- { $0.isCompleted == false }) var items: [Item]
-```
-
-**Dynamic queries:**
-```swift
-struct SearchableItemList: View {
- @Query var items: [Item]
-
- init(searchText: String) {
- let predicate = #Predicate
- { item in
- searchText.isEmpty || item.name.localizedStandardContains(searchText)
- }
- _items = Query(filter: predicate)
- }
-}
-```
-
-**FetchDescriptor for context queries:**
-```swift
-let descriptor = FetchDescriptor
- (
- predicate: #Predicate { $0.isCompleted },
- sortBy: [SortDescriptor(\.timestamp)]
-)
-let items = try context.fetch(descriptor)
-```
-
-
-
-## CRUD Operations
-
-**Create:**
-```swift
-let item = Item(name: "New Item")
-context.insert(item)
-```
-
-**Update:**
-```swift
-item.name = "Updated Name"
-// Changes auto-save
-```
-
-**Delete:**
-```swift
-context.delete(item)
-```
-
-**Manual save:**
-```swift
-try context.save()
-```
-
-
-
-## CloudKit Sync
-
-**Enable in container:**
-```swift
-let config = ModelConfiguration(cloudKitDatabase: .automatic)
-```
-
-**CloudKit constraints:**
-- Cannot use @Attribute(.unique) with CloudKit
-- All properties need defaults or be optional
-- Relationships must be optional
-- Private database only
-
-
-
-## Schema Migration
-
-**Lightweight migration (automatic):**
-- Adding properties with defaults
-- Removing properties
-- Renaming with @Attribute(originalName:)
-
-**Schema versioning:**
-```swift
-enum SchemaV1: VersionedSchema {
- static var versionIdentifier = Schema.Version(1, 0, 0)
- static var models: [any PersistentModel.Type] { [Item.self] }
-}
-```
-
-
-
-## Choosing Your Approach
-
-**New project, iOS 17+ only:** SwiftData
-**Need iOS 16 support:** Core Data
-**Existing Core Data project:** Keep Core Data unless full migration planned
-**Need CloudKit:** SwiftData (simpler) or Core Data (more control)
-
-
-
-## What NOT to Do
-
-
-**Problem:** @Query requires SwiftUI environment
-**Instead:** Use FetchDescriptor with explicit context in view models
-
-
-
-**Problem:** Silently breaks CloudKit sync
-**Instead:** Handle uniqueness in app logic
-
-
-
-**Problem:** Compiles but crashes at runtime
-**Instead:** Use persisted properties for filtering
-
-
diff --git a/src/resources/skills/swiftui/references/testing-debugging.md b/src/resources/skills/swiftui/references/testing-debugging.md
deleted file mode 100644
index 153bb6770..000000000
--- a/src/resources/skills/swiftui/references/testing-debugging.md
+++ /dev/null
@@ -1,247 +0,0 @@
-
-Testing and debugging SwiftUI apps requires a multi-layered approach combining previews, unit tests, UI tests, and debugging tools. SwiftUI's declarative nature makes traditional debugging challenging, but modern tools provide robust solutions.
-
-**Key principles:**
-- Use #Preview macros for rapid visual iteration
-- Test business logic with @Observable view models (not views directly)
-- Write focused UI tests using accessibility identifiers
-- Profile with Instruments on real devices
-
-SwiftUI views cannot be unit tested directly. Test view models and use UI automation tests for interaction testing.
-
-
-
-## Xcode Previews
-
-**Basic #Preview:**
-```swift
-#Preview {
- ContentView()
-}
-
-#Preview("Dark Mode") {
- ContentView()
- .preferredColorScheme(.dark)
-}
-```
-
-**Multiple states:**
-```swift
-#Preview("Empty") { TaskListView(tasks: []) }
-#Preview("Loaded") { TaskListView(tasks: Task.sampleData) }
-#Preview("Error") { TaskListView(tasks: [], error: "Network unavailable") }
-```
-
-**With @Binding (Xcode 16+):**
-```swift
-#Preview {
- @Previewable @State var isOn = true
- ToggleView(isOn: $isOn)
-}
-```
-
-**Mock data:**
-```swift
-extension Task {
- static let sampleData: [Task] = [
- Task(title: "Review PR", isCompleted: false),
- Task(title: "Write tests", isCompleted: true)
- ]
-}
-```
-
-
-
-## Unit Testing View Models
-
-**Testing @Observable with Swift Testing:**
-```swift
-import Testing
-@testable import MyApp
-
-@Test("Login validation")
-func loginValidation() {
- let viewModel = LoginViewModel()
- viewModel.email = ""
- viewModel.password = "password123"
- #expect(!viewModel.isValidInput)
-
- viewModel.email = "user@example.com"
- #expect(viewModel.isValidInput)
-}
-
-@Test("Async data loading")
-func dataLoading() async {
- let mockService = MockService()
- let viewModel = TaskViewModel(service: mockService)
-
- await viewModel.load()
-
- #expect(!viewModel.tasks.isEmpty)
-}
-```
-
-**Dependency injection for testing:**
-```swift
-@Observable
-final class TaskViewModel {
- private let service: TaskServiceProtocol
-
- init(service: TaskServiceProtocol = TaskService()) {
- self.service = service
- }
-}
-```
-
-
-
-## UI Testing
-
-**Setting accessibility identifiers:**
-```swift
-TextField("Email", text: $email)
- .accessibilityIdentifier("emailField")
-
-Button("Login") { }
- .accessibilityIdentifier("loginButton")
-```
-
-**Writing UI tests:**
-```swift
-import XCTest
-
-final class LoginUITests: XCTestCase {
- var app: XCUIApplication!
-
- override func setUp() {
- continueAfterFailure = false
- app = XCUIApplication()
- app.launch()
- }
-
- func testLoginFlow() {
- let emailField = app.textFields["emailField"]
- let loginButton = app.buttons["loginButton"]
-
- XCTAssertTrue(emailField.waitForExistence(timeout: 5))
- emailField.tap()
- emailField.typeText("user@example.com")
-
- loginButton.tap()
-
- let welcomeText = app.staticTexts["welcomeMessage"]
- XCTAssertTrue(welcomeText.waitForExistence(timeout: 5))
- }
-}
-```
-
-
-
-## Debugging Techniques
-
-**_printChanges():**
-```swift
-var body: some View {
- let _ = Self._printChanges()
- VStack { /* content */ }
-}
-```
-
-**View hierarchy debugger:**
-Debug menu → View Debugging → Capture View Hierarchy
-
-**Lifecycle debugging:**
-```swift
-.onAppear { print("View appeared") }
-.onDisappear { print("View disappeared") }
-.task { print("Task started") }
-```
-
-**Visual debugging:**
-```swift
-.border(.red)
-.background(.yellow.opacity(0.3))
-```
-
-
-
-## Instruments Profiling
-
-**SwiftUI template (Xcode 16+):**
-- View Body: Track view creation count
-- View Properties: Monitor property changes
-- Core Animation Commits: Animation performance
-
-**Time Profiler:**
-1. Product → Profile (Cmd+I)
-2. Select Time Profiler
-3. Record while using app
-4. Sort by "Self" time to find hotspots
-
-**Allocations:**
-- Track memory usage
-- Filter by "Persistent" to find leaks
-
-**Always profile on real devices, not simulators.**
-
-
-
-## Common SwiftUI Bugs
-
-**View not updating:**
-```swift
-// Problem: missing @State
-var count = 0 // Won't trigger updates
-
-// Fix: use @State
-@State private var count = 0
-```
-
-**ForEach crash on empty binding:**
-```swift
-// Problem: binding crashes on empty
-ForEach($items) { $item in }
-
-// Fix: check for empty
-if !items.isEmpty {
- ForEach($items) { $item in }
-}
-```
-
-**Animation not working:**
-```swift
-// Problem: no value parameter
-.animation(.spring())
-
-// Fix: specify value
-.animation(.spring(), value: isExpanded)
-```
-
-
-
-## Testing Strategy
-
-**Preview:** Visual iteration, different states
-**Unit Test:** @Observable view models, business logic
-**UI Test:** Critical user flows, login, checkout
-**Manual Test:** Animations, accessibility, performance
-
-
-
-## What NOT to Do
-
-
-**Problem:** Trying to unit test SwiftUI views directly
-**Instead:** Extract logic to view models, test those
-
-
-
-**Problem:** Using text to find elements in UI tests
-**Instead:** Use .accessibilityIdentifier("stableId")
-
-
-
-**Problem:** Hardcoded dependencies in view models
-**Instead:** Use protocols, inject mocks in tests
-
-
diff --git a/src/resources/skills/swiftui/references/uikit-appkit-interop.md b/src/resources/skills/swiftui/references/uikit-appkit-interop.md
deleted file mode 100644
index cf4268252..000000000
--- a/src/resources/skills/swiftui/references/uikit-appkit-interop.md
+++ /dev/null
@@ -1,218 +0,0 @@
-
-SwiftUI wraps UIKit on iOS and AppKit on macOS. Interoperability enables using UIKit/AppKit features not yet available in SwiftUI, and incrementally adopting SwiftUI in existing projects.
-
-**Bridging patterns:**
-- **SwiftUI → UIKit/AppKit**: UIViewRepresentable, NSViewRepresentable, UIViewControllerRepresentable
-- **UIKit/AppKit → SwiftUI**: UIHostingController, NSHostingController/NSHostingView
-- **Coordinator pattern**: Bridge delegates and target-action patterns to SwiftUI
-
-**When to read this:**
-- Wrapping UIKit views not available in SwiftUI
-- Embedding SwiftUI in existing UIKit apps
-- Handling delegate-based APIs
-
-
-
-## UIViewRepresentable
-
-**Basic structure:**
-```swift
-struct CustomTextField: UIViewRepresentable {
- @Binding var text: String
-
- func makeUIView(context: Context) -> UITextField {
- let textField = UITextField()
- textField.delegate = context.coordinator
- return textField
- }
-
- func updateUIView(_ uiView: UITextField, context: Context) {
- if uiView.text != text {
- uiView.text = text
- }
- }
-
- func makeCoordinator() -> Coordinator {
- Coordinator(self)
- }
-
- class Coordinator: NSObject, UITextFieldDelegate {
- var parent: CustomTextField
-
- init(_ parent: CustomTextField) {
- self.parent = parent
- }
-
- func textFieldDidChangeSelection(_ textField: UITextField) {
- parent.text = textField.text ?? ""
- }
- }
-}
-```
-
-**Lifecycle:**
-- `makeUIView` - called once when created
-- `updateUIView` - called when SwiftUI state changes
-- `dismantleUIView` - optional cleanup
-
-
-
-## UIViewControllerRepresentable
-
-```swift
-struct ImagePicker: UIViewControllerRepresentable {
- @Binding var image: UIImage?
- @Environment(\.dismiss) var dismiss
-
- func makeUIViewController(context: Context) -> UIImagePickerController {
- let picker = UIImagePickerController()
- picker.delegate = context.coordinator
- return picker
- }
-
- func updateUIViewController(_ uiViewController: UIImagePickerController, context: Context) {}
-
- func makeCoordinator() -> Coordinator {
- Coordinator(self)
- }
-
- class Coordinator: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
- let parent: ImagePicker
-
- init(_ parent: ImagePicker) {
- self.parent = parent
- }
-
- func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
- parent.image = info[.originalImage] as? UIImage
- parent.dismiss()
- }
- }
-}
-```
-
-
-
-## NSViewRepresentable (macOS)
-
-Same pattern as UIViewRepresentable:
-
-```swift
-struct ColorWell: NSViewRepresentable {
- @Binding var color: NSColor
-
- func makeNSView(context: Context) -> NSColorWell {
- let colorWell = NSColorWell()
- colorWell.target = context.coordinator
- colorWell.action = #selector(Coordinator.colorDidChange(_:))
- return colorWell
- }
-
- func updateNSView(_ nsView: NSColorWell, context: Context) {
- nsView.color = color
- }
-
- func makeCoordinator() -> Coordinator {
- Coordinator(self)
- }
-
- class Coordinator: NSObject {
- var parent: ColorWell
-
- init(_ parent: ColorWell) {
- self.parent = parent
- }
-
- @objc func colorDidChange(_ sender: NSColorWell) {
- parent.color = sender.color
- }
- }
-}
-```
-
-
-
-## UIHostingController
-
-**Embedding SwiftUI in UIKit:**
-```swift
-class MainViewController: UIViewController {
- override func viewDidLoad() {
- super.viewDidLoad()
-
- let swiftUIView = MySwiftUIView()
- let hostingController = UIHostingController(rootView: swiftUIView)
-
- addChild(hostingController)
- view.addSubview(hostingController.view)
-
- hostingController.view.translatesAutoresizingMaskIntoConstraints = false
- NSLayoutConstraint.activate([
- hostingController.view.topAnchor.constraint(equalTo: view.topAnchor),
- hostingController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
- hostingController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor),
- hostingController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor)
- ])
-
- hostingController.didMove(toParent: self)
- }
-}
-```
-
-
-
-## Coordinator Pattern
-
-**When to use:**
-- Handling delegate callbacks
-- Managing target-action patterns
-- Bridging imperative events to SwiftUI
-
-**Structure:**
-```swift
-func makeCoordinator() -> Coordinator {
- Coordinator(self)
-}
-
-class Coordinator: NSObject, SomeDelegate {
- var parent: ParentView
-
- init(_ parent: ParentView) {
- self.parent = parent
- }
-}
-```
-
-
-
-## When to Use Interop
-
-**Use UIKit/AppKit when:**
-- SwiftUI lacks the feature
-- Performance critical scenarios
-- Integrating existing code
-
-**Stay with pure SwiftUI when:**
-- SwiftUI has native support
-- Xcode Previews matter
-- Cross-platform code needed
-
-
-
-## What NOT to Do
-
-
-**Problem:** Using UIViewRepresentable when SwiftUI works
-**Instead:** Check if SwiftUI added the feature
-
-
-
-**Problem:** Handling delegates without Coordinator
-**Instead:** Always use Coordinator for delegate patterns
-
-
-
-**Problem:** Not managing child view controller properly
-**Instead:** addChild → addSubview → didMove(toParent:)
-
-
diff --git a/src/resources/skills/swiftui/workflows/add-feature.md b/src/resources/skills/swiftui/workflows/add-feature.md
deleted file mode 100644
index 0b3590665..000000000
--- a/src/resources/skills/swiftui/workflows/add-feature.md
+++ /dev/null
@@ -1,191 +0,0 @@
-
-**Read these reference files NOW before starting:**
-1. `../macos-apps/references/cli-workflow.md` - Build, run, test from CLI
-2. `references/architecture.md` - App structure, MVVM patterns
-3. `references/state-management.md` - Property wrappers, @Observable
-
-
-
-## Step 1: Understand Existing Codebase
-
-```bash
-find . -name "*.swift" -type f | head -20
-```
-
-**Identify:**
-- App architecture (MVVM, TCA, etc.)
-- Existing patterns and conventions
-- Navigation approach
-- Dependency injection method
-
-## Step 2: Plan Feature Integration
-
-**Define scope:**
-- What views needed?
-- What state must be managed?
-- Does it need persistence (SwiftData)?
-- Does it need network calls?
-- How does it connect to existing features?
-
-## Step 3: Create Feature Module
-
-Follow existing organization:
-```
-Features/
- YourFeature/
- Views/
- YourFeatureView.swift
- ViewModels/
- YourFeatureViewModel.swift
- Models/
- YourFeatureModel.swift
-```
-
-## Step 4: Implement View Model
-
-```swift
-@Observable
-final class YourFeatureViewModel {
- var items: [YourModel] = []
- var isLoading = false
- var errorMessage: String?
-
- private let dataService: DataService
-
- init(dataService: DataService) {
- self.dataService = dataService
- }
-
- func loadData() async {
- isLoading = true
- defer { isLoading = false }
-
- do {
- items = try await dataService.fetchItems()
- } catch {
- errorMessage = error.localizedDescription
- }
- }
-}
-```
-
-## Step 5: Implement Views
-
-```swift
-struct YourFeatureView: View {
- @State private var viewModel: YourFeatureViewModel
-
- init(viewModel: YourFeatureViewModel) {
- self.viewModel = viewModel
- }
-
- var body: some View {
- List(viewModel.items) { item in
- NavigationLink(value: item) {
- YourItemRow(item: item)
- }
- }
- .navigationTitle("Feature Title")
- .navigationDestination(for: YourModel.self) { item in
- YourFeatureDetailView(item: item)
- }
- .task {
- await viewModel.loadData()
- }
- }
-}
-```
-
-## Step 6: Wire Up Navigation
-
-**NavigationStack routing:**
-```swift
-NavigationLink(value: NavigationDestination.yourFeature) {
- Text("Go to Feature")
-}
-
-.navigationDestination(for: NavigationDestination.self) { destination in
- switch destination {
- case .yourFeature:
- YourFeatureView(viewModel: viewModel)
- }
-}
-```
-
-**Sheet presentation:**
-```swift
-@State private var showingFeature = false
-
-Button("Show") { showingFeature = true }
-.sheet(isPresented: $showingFeature) {
- NavigationStack { YourFeatureView(viewModel: viewModel) }
-}
-```
-
-## Step 7: Build and Verify
-
-```bash
-# 1. Build
-xcodebuild -scheme AppName build 2>&1 | xcsift
-
-# 2. Run tests
-xcodebuild -scheme AppName test 2>&1 | xcsift
-
-# 3. Launch and monitor
-# macOS:
-open ./build/Build/Products/Debug/AppName.app
-log stream --predicate 'subsystem == "com.yourcompany.appname"' --level debug
-
-# iOS Simulator:
-xcrun simctl boot "iPhone 15 Pro" 2>/dev/null || true
-xcrun simctl install booted ./build/Build/Products/Debug-iphonesimulator/AppName.app
-xcrun simctl launch booted com.yourcompany.appname
-```
-
-Report to user:
-- "Build: ✓"
-- "Tests: X pass, 0 fail"
-- "Feature added. Ready for you to test [navigation path to feature]"
-
-**User verifies:**
-- Navigate to feature from all entry points
-- Test interactions
-- Check loading/error states
-- Verify light and dark mode
-
-
-
-## Avoid These Mistakes
-
-**Not following existing patterns:**
-- Creating new navigation when project has established pattern
-- Using different naming conventions
-- Introducing new DI when project has standard
-
-**Overengineering:**
-- Adding abstraction that doesn't exist elsewhere
-- Creating generic solutions for specific problems
-- Breaking single view into dozens of tiny files prematurely
-
-**Tight coupling:**
-- Accessing other features' view models directly
-- Hardcoding dependencies
-- Circular dependencies between features
-
-**Breaking existing functionality:**
-- Modifying shared view models without checking all callers
-- Changing navigation state structure
-- Removing @Environment values other views depend on
-
-
-
-This workflow is complete when:
-- [ ] Feature matches existing architecture patterns
-- [ ] Views compose with existing navigation
-- [ ] State management follows project conventions
-- [ ] Dependency injection consistent with existing code
-- [ ] All existing tests pass
-- [ ] No compiler warnings introduced
-- [ ] Error states handled gracefully
-- [ ] Code follows existing naming conventions
-
diff --git a/src/resources/skills/swiftui/workflows/build-new-app.md b/src/resources/skills/swiftui/workflows/build-new-app.md
deleted file mode 100644
index 2ea53e548..000000000
--- a/src/resources/skills/swiftui/workflows/build-new-app.md
+++ /dev/null
@@ -1,311 +0,0 @@
-
-**Read these reference files NOW before starting:**
-1. `../macos-apps/references/project-scaffolding.md` - XcodeGen templates and file structure
-2. `../macos-apps/references/cli-workflow.md` - Build/run/test from CLI
-3. `references/architecture.md` - MVVM patterns and project structure
-4. `references/state-management.md` - Property wrappers
-
-
-
-## Step 1: Clarify Requirements
-
-Ask the user:
-- What does the app do? (core functionality)
-- Which platform? (iOS, macOS, or both)
-- Any specific features needed? (persistence, networking, system integration)
-
-## Step 2: Scaffold Project with XcodeGen
-
-```bash
-# Create directory structure
-mkdir -p AppName/Sources AppName/Tests AppName/Resources
-cd AppName
-
-# Create project.yml (see ../macos-apps/references/project-scaffolding.md for full template)
-cat > project.yml << 'EOF'
-name: AppName
-options:
- bundleIdPrefix: com.yourcompany
- deploymentTarget:
- iOS: "17.0"
- macOS: "14.0"
- xcodeVersion: "15.0"
- createIntermediateGroups: true
-
-targets:
- AppName:
- type: application
- platform: iOS # or macOS, or [iOS, macOS] for multi-platform
- sources: [Sources]
- resources: [Resources]
- settings:
- base:
- PRODUCT_BUNDLE_IDENTIFIER: com.yourcompany.appname
- DEVELOPMENT_TEAM: YOURTEAMID
- SWIFT_VERSION: "5.9"
-
- AppNameTests:
- type: bundle.unit-test
- platform: iOS
- sources: [Tests]
- dependencies:
- - target: AppName
-
-schemes:
- AppName:
- build:
- targets:
- AppName: all
- AppNameTests: [test]
- test:
- targets: [AppNameTests]
-EOF
-
-# Generate xcodeproj
-xcodegen generate
-
-# Verify
-xcodebuild -list -project AppName.xcodeproj
-```
-
-## Step 3: Create Source Files
-
-```
-Sources/
-├── AppNameApp.swift # App entry point
-├── ContentView.swift # Main view
-├── Models/
-├── ViewModels/
-├── Views/
-│ ├── Screens/
-│ └── Components/
-├── Services/
-└── Info.plist
-```
-
-## Step 4: Configure App Entry Point
-
-```swift
-import SwiftUI
-
-@main
-struct YourAppNameApp: App {
- var body: some Scene {
- WindowGroup {
- ContentView()
- }
- }
-}
-```
-
-## Step 5: Create Base Navigation
-
-**Tab-based app:**
-```swift
-struct MainTabView: View {
- var body: some View {
- TabView {
- HomeView()
- .tabItem { Label("Home", systemImage: "house") }
- SettingsView()
- .tabItem { Label("Settings", systemImage: "gear") }
- }
- }
-}
-```
-
-**Stack-based navigation:**
-```swift
-struct RootView: View {
- var body: some View {
- NavigationStack {
- HomeView()
- }
- }
-}
-```
-
-## Step 6: Implement First View Model
-
-```swift
-import Foundation
-import Observation
-
-@Observable
-final class HomeViewModel {
- var items: [Item] = []
- var isLoading = false
- var errorMessage: String?
-
- func loadData() async {
- isLoading = true
- defer { isLoading = false }
-
- do {
- // items = try await service.fetchItems()
- } catch {
- errorMessage = error.localizedDescription
- }
- }
-}
-```
-
-## Step 7: Create Main View
-
-```swift
-struct HomeView: View {
- @State private var viewModel = HomeViewModel()
-
- var body: some View {
- List(viewModel.items) { item in
- Text(item.name)
- }
- .navigationTitle("Home")
- .overlay {
- if viewModel.isLoading { ProgressView() }
- }
- .task {
- await viewModel.loadData()
- }
- }
-}
-
-#Preview {
- NavigationStack { HomeView() }
-}
-```
-
-## Step 8: Wire Up Dependencies
-
-```swift
-@Observable
-final class AppDependencies {
- let apiService: APIService
-
- static let shared = AppDependencies()
-
- private init() {
- self.apiService = APIService()
- }
-}
-```
-
-Inject in App:
-```swift
-@main
-struct YourAppNameApp: App {
- @State private var dependencies = AppDependencies.shared
-
- var body: some Scene {
- WindowGroup {
- ContentView()
- .environment(dependencies)
- }
- }
-}
-```
-
-## Step 9: Build and Verify
-
-```bash
-# Build with error parsing
-xcodebuild -scheme AppName -destination 'platform=iOS Simulator,name=iPhone 15 Pro' build 2>&1 | xcsift
-
-# Boot simulator and install
-xcrun simctl boot "iPhone 15 Pro" 2>/dev/null || true
-xcrun simctl install booted ./build/Build/Products/Debug-iphonesimulator/AppName.app
-
-# Launch and stream logs
-xcrun simctl launch booted com.yourcompany.appname
-log stream --predicate 'subsystem == "com.yourcompany.appname"' --level debug
-```
-
-For macOS apps:
-```bash
-xcodebuild -scheme AppName build 2>&1 | xcsift
-open ./build/Build/Products/Debug/AppName.app
-```
-
-Report to user:
-- "Build: ✓"
-- "App installed on simulator, launching now"
-- "Ready for you to check [specific functionality]"
-
-
-
-## Avoid These Mistakes
-
-**Using NavigationView:**
-```swift
-// DON'T
-NavigationView { ContentView() }
-
-// DO
-NavigationStack { ContentView() }
-```
-
-**Using ObservableObject for new code:**
-```swift
-// DON'T
-class ViewModel: ObservableObject {
- @Published var data = []
-}
-
-// DO
-@Observable
-final class ViewModel {
- var data = []
-}
-```
-
-**Massive views:**
-```swift
-// DON'T
-struct HomeView: View {
- var body: some View {
- VStack { /* 300 lines */ }
- }
-}
-
-// DO
-struct HomeView: View {
- var body: some View {
- VStack {
- HeaderComponent()
- ContentList()
- FooterActions()
- }
- }
-}
-```
-
-**Missing previews:**
-```swift
-// Always add previews for iteration
-#Preview { HomeView() }
-```
-
-**Business logic in views:**
-```swift
-// Move to view model
-struct ProductView: View {
- @State private var viewModel = ProductViewModel()
-
- var body: some View {
- Button("Buy") { Task { await viewModel.purchase() } }
- }
-}
-```
-
-
-
-This workflow is complete when:
-- [ ] Project builds without errors
-- [ ] Folder structure matches MVVM pattern
-- [ ] Navigation set up with NavigationStack or TabView
-- [ ] At least one @Observable view model exists
-- [ ] Dependencies injected via @Environment
-- [ ] No deprecated APIs (NavigationView, ObservableObject)
-- [ ] SwiftUI previews render correctly
-- [ ] App launches without warnings
-
diff --git a/src/resources/skills/swiftui/workflows/debug-swiftui.md b/src/resources/skills/swiftui/workflows/debug-swiftui.md
deleted file mode 100644
index 53b5edb4f..000000000
--- a/src/resources/skills/swiftui/workflows/debug-swiftui.md
+++ /dev/null
@@ -1,192 +0,0 @@
-
-**Read these reference files NOW before starting:**
-1. `../macos-apps/references/cli-observability.md` - Log streaming, crash analysis, LLDB, memory debugging
-2. `references/testing-debugging.md` - SwiftUI-specific debugging techniques
-3. `references/state-management.md` - State management issues are #1 bug source
-
-
-
-## Step 1: Reproduce the Bug Consistently
-
-**Isolate the issue:**
-- Create minimal reproducible example
-- Remove unrelated views and logic
-- Test in both preview and simulator/device
-
-**Document:**
-- What action triggers it?
-- Every time or intermittent?
-- Which platforms/OS versions?
-
-## Step 2: Identify Bug Category
-
-**State Management (60% of bugs):**
-- View not updating
-- Infinite update loops
-- @State/@Binding incorrect usage
-- Missing @Observable
-
-**Layout Issues:**
-- Views not appearing
-- Wrong positioning
-- ScrollView/List sizing problems
-
-**Navigation Issues:**
-- Stack corruption
-- Sheets not dismissing
-- Deep linking breaking
-
-**Performance Issues:**
-- UI freezing
-- Excessive redraws
-- Memory leaks
-
-## Step 3: Add Observability
-
-**Add _printChanges() to suspect view:**
-```swift
-var body: some View {
- let _ = Self._printChanges()
- // rest of view
-}
-```
-This prints exactly which property caused the view to redraw.
-
-**Add logging for runtime visibility:**
-```swift
-import os
-private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "Debug")
-
-// In your code:
-logger.debug("State changed: \(self.items.count) items")
-```
-
-**Stream logs from CLI:**
-```bash
-# While app is running
-log stream --predicate 'subsystem == "com.yourcompany.appname"' --level debug
-
-# Search historical logs
-log show --predicate 'subsystem == "com.yourcompany.appname"' --last 1h
-```
-
-## Step 4: Check Common Causes
-
-**State red flags:**
-- Mutating @State from outside owning view
-- Using @StateObject when should use @Observable
-- Missing @Bindable for passing bindings
-
-**View identity issues:**
-- Array index as id when order changes
-- Missing .id() when identity should reset
-- Same id for different content
-
-**Environment problems:**
-- Custom @Environment not provided
-- Using deprecated @EnvironmentObject
-
-## Step 5: Apply Fix
-
-**State fix:**
-```swift
-// Wrong: ObservableObject
-class ViewModel: ObservableObject {
- @Published var count = 0
-}
-
-// Right: @Observable
-@Observable
-class ViewModel {
- var count = 0
-}
-```
-
-**View identity fix:**
-```swift
-// Wrong: index as id
-ForEach(items.indices, id: \.self) { index in }
-
-// Right: stable id
-ForEach(items) { item in }
-```
-
-**Navigation fix:**
-```swift
-// Wrong: NavigationView
-NavigationView { }
-
-// Right: NavigationStack
-NavigationStack { }
-```
-
-## Step 6: Verify Fix from CLI
-
-```bash
-# 1. Rebuild
-xcodebuild -scheme AppName build 2>&1 | xcsift
-
-# 2. Run tests
-xcodebuild -scheme AppName test 2>&1 | xcsift
-
-# 3. Launch and monitor
-open ./build/Build/Products/Debug/AppName.app
-log stream --predicate 'subsystem == "com.yourcompany.appname"' --level debug
-
-# 4. Check for memory leaks
-leaks AppName
-
-# 5. If crash occurred, check crash logs
-ls ~/Library/Logs/DiagnosticReports/ | grep AppName
-cat ~/Library/Logs/DiagnosticReports/AppName_*.ips | head -100
-```
-
-**For deep debugging, attach LLDB:**
-```bash
-lldb -n AppName
-(lldb) breakpoint set --file ContentView.swift --line 42
-(lldb) continue
-```
-
-Report to user:
-- "Bug no longer reproduces after [specific fix]"
-- "Tests pass: X pass, 0 fail"
-- "No memory leaks detected"
-- "Ready for you to verify the fix"
-
-
-
-## Avoid These Mistakes
-
-**Random changes:**
-- Trying property wrappers without understanding
-- Adding .id(UUID()) hoping it fixes things
-- Wrapping in DispatchQueue.main.async as band-aid
-
-**Ignoring root cause:**
-- Hiding warnings instead of fixing
-- Working around instead of fixing architecture
-
-**Skipping _printChanges():**
-- For state bugs, this is the fastest diagnostic
-- Running this FIRST saves hours
-
-**Using deprecated APIs:**
-- Fix bugs in ObservableObject? Migrate to @Observable
-- NavigationView bugs? Switch to NavigationStack
-
-**Mutating state in body:**
-- Never change @State during body computation
-- Move to .task, .onChange, or button actions
-
-
-
-This workflow is complete when:
-- [ ] Bug is reproducible (or documented as intermittent)
-- [ ] Root cause identified using _printChanges() or other tool
-- [ ] Fix applied following SwiftUI best practices
-- [ ] Bug no longer occurs
-- [ ] No new bugs introduced
-- [ ] Tested on all target platforms
-- [ ] Console shows no related warnings
-
diff --git a/src/resources/skills/swiftui/workflows/optimize-performance.md b/src/resources/skills/swiftui/workflows/optimize-performance.md
deleted file mode 100644
index 2803240e6..000000000
--- a/src/resources/skills/swiftui/workflows/optimize-performance.md
+++ /dev/null
@@ -1,197 +0,0 @@
-
-**Read these reference files NOW before starting:**
-1. `../macos-apps/references/cli-observability.md` - xctrace profiling, leak detection, memory debugging
-2. `references/performance.md` - Profiling, lazy loading, view identity, optimization
-3. `references/layout-system.md` - Layout containers and GeometryReader pitfalls
-
-
-
-## Step 1: Establish Performance Baseline
-
-```bash
-# Build release for accurate profiling
-xcodebuild -scheme AppName -configuration Release build 2>&1 | xcsift
-
-# List available profiling templates
-xcrun xctrace list templates
-
-# Time Profiler - CPU usage baseline
-xcrun xctrace record \
- --template 'Time Profiler' \
- --time-limit 30s \
- --output baseline-cpu.trace \
- --launch -- ./build/Build/Products/Release/AppName.app/Contents/MacOS/AppName
-
-# SwiftUI template (if available)
-xcrun xctrace record \
- --template 'SwiftUI' \
- --time-limit 30s \
- --output baseline-swiftui.trace \
- --launch -- ./build/Build/Products/Release/AppName.app/Contents/MacOS/AppName
-
-# Export trace data
-xcrun xctrace export --input baseline-cpu.trace --toc
-```
-
-Document baseline: CPU usage, view update count, frame rate during slow flows.
-
-## Step 2: Profile View Updates
-
-Add to suspect views:
-```swift
-var body: some View {
- let _ = Self._printChanges()
- // rest of view
-}
-```
-
-Check console for which properties caused invalidation.
-
-## Step 3: Fix Unnecessary View Recreation
-
-**Stable view identity:**
-```swift
-// Wrong: index as id
-ForEach(items.indices, id: \.self) { }
-
-// Right: stable id
-ForEach(items) { item in
- ItemRow(item: item).id(item.id)
-}
-```
-
-**Isolate frequently-changing state:**
-```swift
-// Before: entire list recreates
-struct SlowList: View {
- @State private var items: [Item] = []
- @State private var count: Int = 0 // Updates often
-
- var body: some View {
- List(items) { item in ItemRow(item: item) }
- }
-}
-
-// After: isolate count to separate view
-struct FastList: View {
- @State private var items: [Item] = []
-
- var body: some View {
- VStack {
- CountBadge() // Only this updates
- List(items) { item in ItemRow(item: item) }
- }
- }
-}
-```
-
-## Step 4: Optimize Lists
-
-```swift
-// Use lazy containers
-ScrollView {
- LazyVStack(spacing: 8) {
- ForEach(items) { item in
- ItemRow(item: item)
- }
- }
-}
-```
-
-## Step 5: Reduce Layout Passes
-
-```swift
-// Avoid GeometryReader when possible
-// Before:
-GeometryReader { geo in
- Circle().frame(width: geo.size.width * 0.8)
-}
-
-// After:
-Circle()
- .frame(maxWidth: .infinity)
- .aspectRatio(1, contentMode: .fit)
- .padding(.horizontal, 20)
-```
-
-## Step 6: Use @Observable
-
-```swift
-// Before: ObservableObject invalidates everything
-class OldViewModel: ObservableObject {
- @Published var name = ""
- @Published var count = 0
-}
-
-// After: granular updates
-@Observable
-class ViewModel {
- var name = ""
- var count = 0
-}
-```
-
-## Step 7: Verify Improvements from CLI
-
-```bash
-# 1. Rebuild release
-xcodebuild -scheme AppName -configuration Release build 2>&1 | xcsift
-
-# 2. Profile again with same settings
-xcrun xctrace record \
- --template 'Time Profiler' \
- --time-limit 30s \
- --output optimized-cpu.trace \
- --launch -- ./build/Build/Products/Release/AppName.app/Contents/MacOS/AppName
-
-# 3. Check for memory leaks
-leaks AppName
-
-# 4. Run tests to ensure no regressions
-xcodebuild test -scheme AppName 2>&1 | xcsift
-
-# 5. Launch for user verification
-open ./build/Build/Products/Release/AppName.app
-```
-
-Report to user:
-- "CPU usage reduced from X% to Y%"
-- "View body invocations reduced by Z%"
-- "No memory leaks detected"
-- "Tests: all pass, no regressions"
-- "App launched - please verify scrolling feels smooth"
-
-
-
-## Avoid These Mistakes
-
-**Optimizing without profiling:**
-- Always measure with Instruments first
-- Let data guide decisions
-
-**Using .equatable() as first resort:**
-- Masks the issue instead of fixing it
-- Can cause stale UI
-
-**Testing only in simulator:**
-- Simulator runs on Mac CPU
-- Always profile on real devices
-
-**Ignoring view identity:**
-- Use explicit id() when needed
-- Ensure stable IDs in ForEach
-
-**Premature view extraction:**
-- Extract when it isolates state observation
-- Not "for performance" by default
-
-
-
-This workflow is complete when:
-- [ ] Time Profiler shows reduced CPU usage
-- [ ] 50%+ reduction in unnecessary view body invocations
-- [ ] Scroll performance at 60fps
-- [ ] App feels responsive on oldest supported device
-- [ ] Memory usage stable, no leaks
-- [ ] _printChanges() confirms targeted updates
-
diff --git a/src/resources/skills/swiftui/workflows/ship-app.md b/src/resources/skills/swiftui/workflows/ship-app.md
deleted file mode 100644
index 227bcc3ba..000000000
--- a/src/resources/skills/swiftui/workflows/ship-app.md
+++ /dev/null
@@ -1,203 +0,0 @@
-
-**Read these reference files NOW before starting:**
-1. `../macos-apps/references/cli-workflow.md` - Build, test, sign, notarize from CLI
-2. `../macos-apps/references/security-code-signing.md` - Code signing and notarization
-3. `references/platform-integration.md` - iOS/macOS specifics, platform requirements
-
-
-
-## Step 1: Run Tests
-
-```bash
-# iOS
-xcodebuild test -scheme AppName -destination 'platform=iOS Simulator,name=iPhone 15 Pro' 2>&1 | xcsift
-
-# macOS
-xcodebuild test -scheme AppName 2>&1 | xcsift
-```
-
-All tests must pass before shipping.
-
-## Step 2: Profile Performance from CLI
-
-```bash
-# Build release for accurate profiling
-xcodebuild -scheme AppName -configuration Release build 2>&1 | xcsift
-
-# Time Profiler
-xcrun xctrace record \
- --template 'Time Profiler' \
- --time-limit 30s \
- --output ship-profile.trace \
- --launch -- ./build/Build/Products/Release/AppName.app/Contents/MacOS/AppName
-
-# Check for leaks
-leaks AppName
-
-# Memory allocations
-xcrun xctrace record \
- --template 'Allocations' \
- --time-limit 30s \
- --output ship-allocations.trace \
- --attach $(pgrep AppName)
-```
-
-Report: "No memory leaks. CPU usage acceptable. Ready to ship."
-
-## Step 3: Update Version Numbers
-
-```bash
-# Marketing version
-/usr/libexec/PlistBuddy -c "Set :CFBundleShortVersionString 1.0.0" "YourApp/Info.plist"
-
-# Build number (must increment each submission)
-/usr/libexec/PlistBuddy -c "Set :CFBundleVersion 1" "YourApp/Info.plist"
-```
-
-## Step 4: Create Privacy Manifest
-
-Create `PrivacyInfo.xcprivacy` with all accessed APIs:
-- NSPrivacyAccessedAPICategoryUserDefaults
-- NSPrivacyAccessedAPICategoryFileTimestamp
-- etc.
-
-Required for iOS 17+ and macOS 14+.
-
-## Step 5: Verify App Icons
-
-All required sizes in Assets.xcassets:
-- 1024x1024 App Store icon (required)
-- All device sizes filled
-
-## Step 6: Configure Code Signing
-
-Set in project.yml (XcodeGen) or verify existing settings:
-```yaml
-settings:
- base:
- CODE_SIGN_STYLE: Automatic
- DEVELOPMENT_TEAM: YOURTEAMID
- CODE_SIGN_IDENTITY: "Apple Distribution"
-```
-
-Or set via xcodebuild:
-```bash
-xcodebuild -scheme AppName \
- CODE_SIGN_STYLE=Automatic \
- DEVELOPMENT_TEAM=YOURTEAMID \
- archive
-```
-
-## Step 7: Create Archive
-
-```bash
-xcodebuild archive \
- -scheme YourApp \
- -configuration Release \
- -archivePath ./build/YourApp.xcarchive \
- -destination 'generic/platform=iOS'
-```
-
-## Step 8: Export for App Store
-
-```bash
-xcodebuild -exportArchive \
- -archivePath ./build/YourApp.xcarchive \
- -exportPath ./build/Export \
- -exportOptionsPlist ExportOptions.plist
-```
-
-## Step 9: Create App in App Store Connect
-
-1. Visit appstoreconnect.apple.com
-2. My Apps → + → New App
-3. Fill in name, bundle ID, SKU
-
-## Step 10: Upload Build from CLI
-
-```bash
-# Validate before upload
-xcrun altool --validate-app -f ./build/Export/AppName.ipa -t ios --apiKey YOUR_KEY --apiIssuer YOUR_ISSUER
-
-# Upload to App Store Connect
-xcrun altool --upload-app -f ./build/Export/AppName.ipa -t ios --apiKey YOUR_KEY --apiIssuer YOUR_ISSUER
-
-# For macOS apps, notarize first (see ../macos-apps/references/security-code-signing.md)
-xcrun notarytool submit AppName.zip --apple-id your@email.com --team-id TEAMID --password @keychain:AC_PASSWORD --wait
-xcrun stapler staple AppName.app
-```
-
-Alternative: Use Transporter app if API keys aren't set up.
-
-## Step 11: Complete Metadata
-
-In App Store Connect:
-- Description (4000 char max)
-- Keywords (100 char max)
-- Screenshots (at least 1 per device type)
-- Privacy Policy URL
-- Support URL
-
-## Step 12: Configure TestFlight (Optional)
-
-1. Wait for build processing
-2. Add internal testers (up to 100)
-3. For external testing, submit for Beta App Review
-
-## Step 13: Submit for Review
-
-1. Select processed build
-2. Complete App Review Information
-3. Provide demo account if login required
-4. Submit for Review
-
-Review typically completes in 24-48 hours.
-
-## Step 14: Handle Outcome
-
-**If approved:** Release manually or automatically
-
-**If rejected:**
-- Read rejection reason
-- Fix issues
-- Increment build number
-- Re-upload and resubmit
-
-
-
-## Avoid These Mistakes
-
-**Testing only in simulator:**
-- Always test on physical devices before submission
-
-**Incomplete privacy manifest:**
-- Document all accessed APIs
-- Use Xcode's Privacy Report
-
-**Same build number:**
-- Must increment CFBundleVersion for each upload
-
-**Debug code in release:**
-- Remove NSLog, test accounts, debug views
-- Use #if DEBUG
-
-**Screenshots of splash screen:**
-- Must show app in actual use
-- Guideline 2.3.3 rejection risk
-
-**Not testing exported build:**
-- Export process applies different signing
-- Apps can crash after export despite working in Xcode
-
-
-
-This workflow is complete when:
-- [ ] All tests pass
-- [ ] Version and build numbers updated
-- [ ] Privacy manifest complete
-- [ ] Archive created successfully
-- [ ] Build uploaded to App Store Connect
-- [ ] Metadata and screenshots complete
-- [ ] App submitted for review
-- [ ] App approved and live on App Store
-
diff --git a/src/resources/skills/swiftui/workflows/write-tests.md b/src/resources/skills/swiftui/workflows/write-tests.md
deleted file mode 100644
index a2fa5efb0..000000000
--- a/src/resources/skills/swiftui/workflows/write-tests.md
+++ /dev/null
@@ -1,235 +0,0 @@
-
-**Read these reference files NOW before starting:**
-1. `../macos-apps/references/cli-workflow.md` - Test commands from CLI
-2. `../macos-apps/references/testing-tdd.md` - TDD patterns, avoiding @main hangs
-3. `references/testing-debugging.md` - SwiftUI-specific testing and debugging
-
-
-
-## Step 1: Identify Testing Scope
-
-**Test business logic in view models, not views:**
-```swift
-// Testable view model
-@Observable
-final class LoginViewModel {
- var email = ""
- var password = ""
- var isLoading = false
-
- var isValidInput: Bool {
- !email.isEmpty && password.count >= 8
- }
-}
-
-// View is just presentation
-struct LoginView: View {
- let viewModel: LoginViewModel
- var body: some View {
- Form {
- TextField("Email", text: $viewModel.email)
- Button("Login") { }
- .disabled(!viewModel.isValidInput)
- }
- }
-}
-```
-
-## Step 2: Write Unit Tests
-
-**Using Swift Testing (@Test):**
-```swift
-import Testing
-@testable import MyApp
-
-@Test("Email validation")
-func emailValidation() {
- let viewModel = LoginViewModel()
-
- viewModel.email = ""
- viewModel.password = "password123"
- #expect(!viewModel.isValidInput)
-
- viewModel.email = "user@example.com"
- #expect(viewModel.isValidInput)
-}
-
-@Test("Async loading")
-func asyncLoading() async {
- let mockService = MockService()
- let viewModel = TaskViewModel(service: mockService)
-
- await viewModel.load()
-
- #expect(!viewModel.tasks.isEmpty)
-}
-```
-
-## Step 3: Add Accessibility Identifiers
-
-```swift
-TextField("Email", text: $email)
- .accessibilityIdentifier("emailField")
-
-SecureField("Password", text: $password)
- .accessibilityIdentifier("passwordField")
-
-Button("Login") { }
- .accessibilityIdentifier("loginButton")
-```
-
-## Step 4: Write UI Tests
-
-```swift
-import XCTest
-
-final class LoginUITests: XCTestCase {
- var app: XCUIApplication!
-
- override func setUp() {
- continueAfterFailure = false
- app = XCUIApplication()
- app.launch()
- }
-
- func testLoginFlow() {
- let emailField = app.textFields["emailField"]
- let passwordField = app.secureTextFields["passwordField"]
- let loginButton = app.buttons["loginButton"]
-
- XCTAssertTrue(emailField.waitForExistence(timeout: 5))
-
- emailField.tap()
- emailField.typeText("user@example.com")
-
- passwordField.tap()
- passwordField.typeText("password123")
-
- XCTAssertTrue(loginButton.isEnabled)
- loginButton.tap()
-
- let welcomeText = app.staticTexts["welcomeMessage"]
- XCTAssertTrue(welcomeText.waitForExistence(timeout: 5))
- }
-}
-```
-
-## Step 5: Create Previews for Visual Testing
-
-```swift
-#Preview("Empty") { LoginView(viewModel: LoginViewModel()) }
-
-#Preview("Filled") {
- let viewModel = LoginViewModel()
- viewModel.email = "user@example.com"
- viewModel.password = "password123"
- return LoginView(viewModel: viewModel)
-}
-
-#Preview("Error") {
- let viewModel = LoginViewModel()
- viewModel.errorMessage = "Invalid credentials"
- return LoginView(viewModel: viewModel)
-}
-
-#Preview("Dark Mode") {
- LoginView(viewModel: LoginViewModel())
- .preferredColorScheme(.dark)
-}
-```
-
-## Step 6: Run Tests from CLI
-
-```bash
-# Run all tests with parsed output
-xcodebuild test -scheme AppName -destination 'platform=iOS Simulator,name=iPhone 15 Pro' 2>&1 | xcsift
-
-# Run only unit tests
-xcodebuild test -scheme AppName -only-testing:AppNameTests 2>&1 | xcsift
-
-# Run only UI tests
-xcodebuild test -scheme AppName -only-testing:AppNameUITests 2>&1 | xcsift
-
-# Run specific test class
-xcodebuild test -scheme AppName -only-testing:AppNameTests/LoginViewModelTests 2>&1 | xcsift
-
-# Run specific test method
-xcodebuild test -scheme AppName -only-testing:AppNameTests/LoginViewModelTests/testEmailValidation 2>&1 | xcsift
-
-# Generate test coverage
-xcodebuild test -scheme AppName -enableCodeCoverage YES -resultBundlePath TestResults.xcresult 2>&1 | xcsift
-xcrun xccov view --report TestResults.xcresult
-```
-
-**If tests hang:** The test target likely depends on the app target with `@main`. Extract testable code to a Core framework target. See `../macos-apps/references/testing-tdd.md`.
-
-Report to user:
-- "Tests: X pass, Y fail"
-- "Coverage: Z% of lines"
-- If failures: "Failed tests: [list]. Investigating..."
-
-
-
-## Avoid These Mistakes
-
-**Testing view bodies:**
-```swift
-// Wrong: can't test views directly
-func testView() {
- let view = LoginView()
- // Can't inspect SwiftUI view
-}
-
-// Right: test view model
-@Test func emailInput() {
- let viewModel = LoginViewModel()
- viewModel.email = "test@example.com"
- #expect(viewModel.email == "test@example.com")
-}
-```
-
-**Missing accessibility identifiers:**
-```swift
-// Wrong: using text
-let button = app.buttons["Login"]
-
-// Right: stable identifier
-let button = app.buttons["loginButton"]
-```
-
-**No dependency injection:**
-```swift
-// Wrong: can't mock
-@Observable
-class ViewModel {
- private let service = RealService()
-}
-
-// Right: injectable
-@Observable
-class ViewModel {
- private let service: ServiceProtocol
- init(service: ServiceProtocol) {
- self.service = service
- }
-}
-```
-
-**No edge case testing:**
-```swift
-// Test empty, invalid, error states
-@Test func emptyEmail() { }
-@Test func shortPassword() { }
-@Test func networkError() { }
-```
-
-
-
-This workflow is complete when:
-- [ ] Unit tests verify view model business logic
-- [ ] UI tests verify user flows using accessibility identifiers
-- [ ] All tests pass: `xcodebuild test -scheme YourApp`
-- [ ] Edge cases and error states have coverage
-- [ ] Dependencies use protocols for testability
-- [ ] Previews exist for major UI states
-
diff --git a/src/resources/skills/web-quality-audit/SKILL.md b/src/resources/skills/web-quality-audit/SKILL.md
index a3cc80c60..583aec1cb 100644
--- a/src/resources/skills/web-quality-audit/SKILL.md
+++ b/src/resources/skills/web-quality-audit/SKILL.md
@@ -163,8 +163,6 @@ When performing an audit, structure findings as:
## References
For detailed guidelines on specific areas:
-- [Performance Optimization](../performance/SKILL.md)
- [Core Web Vitals](../core-web-vitals/SKILL.md)
- [Accessibility](../accessibility/SKILL.md)
-- [SEO](../seo/SKILL.md)
- [Best Practices](../best-practices/SKILL.md)