Skip to content
Back to skills

1484 Swiftui Code Review Reference 47b1e0a5

ASecurity

A comprehensive guide for reviewing SwiftUI code, focusing on common anti-patterns, best practices, and critical issues. ---

  • 9 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added October 11, 2026
code-qualitygoswiftawsdebugginggitdatabaseperformancedocumentation

Works with

  • cli

Security analysis

A100/100

Scanned October 11, 2026

npx -y skills add tools-only/X-Skills --skill 1484-swiftui-code-review-reference_47b1e0a5 --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of 1484 Swiftui Code Review Reference 47b1e0a5?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for 1484 Swiftui Code Review Reference 47b1e0a5
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/tools-only-1484-swiftui-code-review-reference-47b1e0a5/badge)](https://www.skillsdirectory.com/skills/tools-only-1484-swiftui-code-review-reference-47b1e0a5)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

SKILL.md
# SwiftUI Code Review Reference

A comprehensive guide for reviewing SwiftUI code, focusing on common anti-patterns, best practices, and critical issues.

---

## Critical Anti-Patterns

### 1. AnyView Type Erasure

AnyView erases type information, preventing SwiftUI's structural identity diffing and causing full view redraws.

```swift
// BAD: Type erasure breaks diffing
func makeView(for type: ContentType) -> some View {
    AnyView(
        switch type {
        case .text: Text("Hello")
        case .image: Image("photo")
        }
    )
}

// GOOD: Use @ViewBuilder to preserve types
@ViewBuilder
func makeView(for type: ContentType) -> some View {
    switch type {
    case .text: Text("Hello")
    case .image: Image("photo")
    }
}

// GOOD: Use Group for conditional views
func makeView(for type: ContentType) -> some View {
    Group {
        switch type {
        case .text: Text("Hello")
        case .image: Image("photo")
        }
    }
}
```

**Why it matters:** SwiftUI uses structural identity to efficiently diff views. AnyView forces complete redraws because the framework cannot determine what changed.

### 2. Wrong Modifier Ordering

Modifier order dramatically affects layout because each modifier wraps the view in a new view.

```swift
// BAD: Background only covers text, not padding
Text("Hello")
    .background(Color.blue)
    .padding()

// GOOD: Background covers padded area
Text("Hello")
    .padding()
    .background(Color.blue)

// BAD: Frame applied before background
Text("Hello")
    .background(Color.red)
    .frame(width: 200, height: 200)
// Result: 200x200 empty square with small red area around text

// GOOD: Background fills the frame
Text("Hello")
    .frame(width: 200, height: 200)
    .background(Color.red)
```

**Why it matters:** Each modifier creates a new wrapper view. The order determines what gets styled/sized.

### 3. VStack in ScrollView with Many Items

VStack renders all children immediately, even those offscreen.

```swift
// BAD: All 1000 rows created at once
ScrollView {
    VStack {
        ForEach(0..<1000, id: \.self) { i in
            ExpensiveRow(index: i)
        }
    }
}

// GOOD: Only visible rows created
ScrollView {
    LazyVStack {
        ForEach(0..<1000, id: \.self) { i in
            ExpensiveRow(index: i)
        }
    }
}
```

**When NOT to use Lazy:** For small, fixed lists where all items fit on screen. Lazy stacks have bookkeeping overhead that isn't worth it for small lists.

### 4. Wrong Property Wrapper for Observable Objects

```swift
// BAD: @ObservedObject for view-owned data (can be recreated)
struct ContentView: View {
    @ObservedObject var model = DataModel()  // Wrong!
    var body: some View { ... }
}

// GOOD: @StateObject for view-owned data (created once)
struct ContentView: View {
    @StateObject var model = DataModel()  // Correct!
    var body: some View { ... }
}

// GOOD: @ObservedObject for passed-in data
struct ChildView: View {
    @ObservedObject var model: DataModel  // Injected from parent
    var body: some View { ... }
}
```

**Rule:** `@StateObject` = view owns and creates the object. `@ObservedObject` = object passed from elsewhere.

### 5. Conditional Views Breaking Structural Identity

```swift
// BAD: Creates two different views with different identities
var body: some View {
    if showDetails {
        DetailView()
            .foregroundColor(.blue)
    } else {
        DetailView()
            .foregroundColor(.gray)
    }
}

// GOOD: Same view, different modifiers (preserves identity)
var body: some View {
    DetailView()
        .foregroundColor(showDetails ? .blue : .gray)
}
```

**Why it matters:** Branches (`if/else`) create different structural identities, triggering animations and state loss.

### 6. Heavy Work in View Body

```swift
// BAD: Expensive computation on every redraw
var body: some View {
    let filtered = items.filter { $0.isActive }
        .sorted { $0.date > $1.date }
    List(filtered) { item in
        ItemRow(item: item)
    }
}

// GOOD: Cache expensive operations
@State private var filteredItems: [Item] = []

var body: some View {
    List(filteredItems) { item in
        ItemRow(item: item)
    }
    .task { filteredItems = await computeFiltered() }
    .onChange(of: items) { filteredItems = computeFiltered() }
}

// BAD: Database/network calls in computed properties
var hasAttachment: Bool {
    loadAttachment() != nil  // Called on every body evaluation!
}

// GOOD: Check only what's necessary
var hasAttachment: Bool {
    attachmentURL != nil  // Fast check
}
```

### 7. Missing Identifiers in Dynamic Lists

```swift
// BAD: Dynamic range without stable identifier
@State private var rowCount = 5

var body: some View {
    List(0..<rowCount) { row in  // Warning: range must be constant
        Text("Row \(row)")
    }
}

// GOOD: Explicit identifier
List(0..<rowCount, id: \.self) { row in
    Text("Row \(row)")
}

// BEST: Use Identifiable data
List(items) { item in  // item.id used automatically
    ItemRow(item: item)
}
```

### 8. Stroke vs StrokeBorder

```swift
// BAD: stroke() centers on edge, may clip
Circle()
    .stroke(Color.blue, lineWidth: 20)

// GOOD: strokeBorder() stays inside bounds
Circle()
    .strokeBorder(Color.blue, lineWidth: 20)
```

---

## State Management Rules

### iOS 17+ (Observation Framework)

Three primary property wrappers:

| Wrapper | Use Case |
|---------|----------|
| `@State` | View-owned data (value OR reference types with @Observable) |
| `@Bindable` | Create bindings to @Observable object properties |
| `@Environment` | Shared data from the environment |

```swift
// @State for view-owned Observable object
@Observable class ViewModel { var count = 0 }

struct ContentView: View {
    @State private var viewModel = ViewModel()  // iOS 17+
    var body: some View {
        Button("\(viewModel.count)") { viewModel.count += 1 }
    }
}

// @Bindable for bindings to Observable properties
struct EditView: View {
    @Environment(User.self) private var user

    var body: some View {
        @Bindable var user = user  // Create bindable reference
        TextField("Name", text: $user.name)
    }
}
```

### Pre-iOS 17 (ObservableObject)

| Wrapper | Use Case |
|---------|----------|
| `@State` | View-owned value types |
| `@StateObject` | View-owned ObservableObject |
| `@ObservedObject` | Injected ObservableObject |
| `@EnvironmentObject` | Shared ObservableObject |
| `@Binding` | Two-way binding to parent state |

### State Ownership Decision Tree

1. **Does the view create and own this data?**
   - Value type -> `@State`
   - Reference type (iOS 17+) -> `@State` with `@Observable`
   - Reference type (pre-iOS 17) -> `@StateObject`

2. **Is this data passed from a parent?**
   - Need to modify -> `@Binding`
   - Just read -> regular property

3. **Is this data shared across the app?**
   - iOS 17+ -> `@Environment` with `@Observable`
   - Pre-iOS 17 -> `@EnvironmentObject`

---

## Performance Optimization

### Use Equatable Views

```swift
// Implement Equatable to control view updates
struct ExpensiveView: View, Equatable {
    let data: ExpensiveData

    var body: some View {
        // Complex view hierarchy
    }

    static func == (lhs: Self, rhs: Self) -> Bool {
        lhs.data.id == rhs.data.id  // Custom equality check
    }
}

// Use .equatable() modifier
ParentView()
    .equatable()  // Uses Equatable conformance for diffing
```

**When to use:** When body computation is more expensive than your equality check.

### Minimize Redraws

```swift
// BAD: Every image download triggers all views to redraw
class ImageLoader: ObservableObject {
    @Published var images = [String: UIImage]()
}

// GOOD: Batch updates
class ImageLoader: ObservableObject {
    private var images = [String: UIImage]()
    private var pendingUpdate = false

    func imageLoaded(_ image: UIImage, for key: String) {
        images[key] = image
        if !pendingUpdate {
            pendingUpdate = true
            DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
                self.objectWillChange.send()
                self.pendingUpdate = false
            }
        }
    }
}
```

### Debug Redraws

```swift
var body: some View {
    let _ = Self._printChanges()  // Prints what triggered redraw
    // ... view content
}
```

### Lazy Container Guidelines

| Container | Use When |
|-----------|----------|
| `VStack`/`HStack` | Small, fixed number of items |
| `LazyVStack`/`LazyHStack` | Scrollable content with many items |
| `List` | Very large datasets (provides cell recycling) |
| `LazyVGrid`/`LazyHGrid` | Grid layouts with many items |

**iOS 18+ Improvement:** LazyVStack now unloads off-screen views, improving memory usage.

---

## View Composition Best Practices

### When to Extract Subviews

1. **Over 50-100 lines** in body -> Extract
2. **Reused in multiple places** -> Extract to separate struct
3. **Has own state** -> Extract to separate struct
4. **Improves readability** -> Extract (even small views)

### Extraction Methods

```swift
// Method 1: Private @ViewBuilder function (local reuse)
struct ContentView: View {
    @ViewBuilder
    private func headerSection() -> some View {
        VStack {
            Text("Header")
            Divider()
        }
    }

    var body: some View {
        VStack {
            headerSection()
            // ...
        }
    }
}

// Method 2: Separate struct (cross-file reuse, has own state)
struct HeaderSection: View {
    let title: String

    var body: some View {
        VStack {
            Text(title)
            Divider()
        }
    }
}

// Method 3: ViewBuilder computed property (simple extraction)
struct ContentView: View {
    @ViewBuilder
    private var headerSection: some View {
        VStack {
            Text("Header")
            Divider()
        }
    }
}
```

### Modifier Grouping

```swift
// Extract common modifier combinations
extension View {
    func cardStyle() -> some View {
        self
            .padding()
            .background(Color(.systemBackground))
            .cornerRadius(12)
            .shadow(radius: 4)
    }
}

// Usage
Text("Card Content")
    .cardStyle()
```

---

## Accessibility Requirements

### Essential Modifiers

```swift
// Labels for non-text elements
Image(systemName: "star.fill")
    .accessibilityLabel("Favorite")

// Hints for actions
Button(action: sendMessage) {
    Image(systemName: "paperplane")
}
.accessibilityLabel("Send")
.accessibilityHint("Double-tap to send the message")

// Traits for custom interactive elements
Image("product")
    .onTapGesture { selectProduct() }
    .accessibilityLabel("Product photo")
    .accessibilityAddTraits(.isButton)
```

### Grouping Elements

```swift
// BAD: VoiceOver reads each element separately
HStack {
    Image(systemName: "star.fill")
    Text("5.0")
    Text("(128 reviews)")
}

// GOOD: Group as single element
HStack {
    Image(systemName: "star.fill")
    Text("5.0")
    Text("(128 reviews)")
}
.accessibilityElement(children: .ignore)
.accessibilityLabel("Rating: 5 stars, 128 reviews")
```

### Dynamic Type Support

```swift
// System fonts automatically scale
Text("Hello")
    .font(.body)  // Scales with Dynamic Type

// Custom fonts must opt-in
Text("Hello")
    .font(.custom("MyFont", size: 17, relativeTo: .body))

// Scale non-text values with @ScaledMetric
@ScaledMetric(relativeTo: .body) private var iconSize: CGFloat = 24

var body: some View {
    Image(systemName: "star")
        .frame(width: iconSize, height: iconSize)
}

// Adapt layout for accessibility sizes
@Environment(\.dynamicTypeSize) var dynamicTypeSize

var body: some View {
    if dynamicTypeSize.isAccessibilitySize {
        VStack { content }  // Stack vertically for large text
    } else {
        HStack { content }  // Horizontal for normal sizes
    }
}
```

### Hiding Decorative Elements

```swift
// Hide purely decorative elements
Image("decorative-divider")
    .accessibilityHidden(true)
```

---

## Navigation (iOS 16+)

```swift
// BAD: Deprecated NavigationView
NavigationView {
    List { ... }
}

// GOOD: NavigationStack for drill-down
NavigationStack {
    List { ... }
}

// GOOD: NavigationSplitView for master-detail
NavigationSplitView {
    Sidebar()
} detail: {
    DetailView()
}
```

---

## Code Review Questions

### 1. State Management
- Is the correct property wrapper used? (`@State` vs `@StateObject` vs `@ObservedObject`)
- Is state declared as `private` when owned by the view?
- Are there multiple sources of truth for the same data?

### 2. Performance
- Are lazy containers used for large scrollable lists?
- Is expensive computation cached rather than in body/computed properties?
- Is AnyView used unnecessarily (should use @ViewBuilder or generics)?
- Could Equatable conformance reduce unnecessary redraws?

### 3. View Composition
- Is the body under 50-100 lines?
- Are reusable components extracted into separate structs?
- Is modifier order correct (padding before background, frame before background)?

### 4. Accessibility
- Do all actionable elements have accessibility labels?
- Are custom tap gestures marked with `.accessibilityAddTraits(.isButton)`?
- Does the UI work with Dynamic Type accessibility sizes?
- Are decorative images hidden from VoiceOver?

### 5. Identity & Lifecycle
- Are dynamic lists using stable identifiers (Identifiable or explicit `id:`)?
- Are conditional branches necessary or can inline modifiers preserve identity?
- Is `.id()` used intentionally and not causing unexpected state loss?

---

## Sources

### Apple Documentation & WWDC
- [SwiftUI Documentation](https://developer.apple.com/documentation/swiftui)
- [WWDC24: Catch up on accessibility in SwiftUI](https://developer.apple.com/videos/play/wwdc2024/10073/)
- [WWDC24: SwiftUI essentials](https://developer.apple.com/videos/play/wwdc2024/10150/)
- [WWDC23: Discover Observation in SwiftUI](https://developer.apple.com/videos/play/wwdc2023/10149/)
- [WWDC21: Demystify SwiftUI](https://developer.apple.com/videos/play/wwdc2021/10022/)

### Community Resources
- [SwiftUI Performance Tips - Martin Mitrevski](https://martinmitrevski.com/2022/04/14/swiftui-performance-tips/)
- [Optimization and Debugging - Fatbobman](https://fatbobman.com/en/collections/optimization-debugging/)
- [8 Common SwiftUI Mistakes - Hacking with Swift](https://www.hackingwithswift.com/articles/224/common-swiftui-mistakes-and-how-to-fix-them)
- [Understanding SwiftUI Performance - Airbnb Engineering](https://medium.com/airbnb-engineering/understanding-and-improving-swiftui-performance-37b77ac61896)
- [Structural identity in SwiftUI - Swift with Majid](https://swiftwithmajid.com/2021/12/09/structural-identity-in-swiftui/)
- [Avoiding massive SwiftUI views - Swift by Sundell](https://www.swiftbysundell.com/articles/avoiding-massive-swiftui-views/)
- [How to avoid using AnyView - Tanaschita](https://tanaschita.com/swiftui-how-to-avoid-using-anyview/)
- [SwiftUI Property Wrappers - Fatbobman](https://fatbobman.com/en/posts/exploring-key-property-wrappers-in-swiftui/)
- [@ScaledMetric Dynamic Type Support - SwiftLee](https://www.avanderlee.com/swiftui/scaledmetric-dynamic-type-support/)
- [Equatable Package for SwiftUI Diffing](https://github.com/ordo-one/equatable)

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.