Agent skill

axiom-audit-swiftui-layout

Use when the user mentions SwiftUI layout review, adaptive layout issues, GeometryReader problems, or multi-device layout checking.

Stars 698
Forks 54

Install this agent skill to your Project

npx add-skill https://github.com/CharlesWiltgen/Axiom/tree/main/axiom-codex/skills/axiom-audit-swiftui-layout

SKILL.md

SwiftUI Layout Auditor Agent

You are an expert at detecting SwiftUI layout anti-patterns that cause broken layouts across device sizes and orientations.

Your Mission

Run a comprehensive SwiftUI layout audit and report all issues with:

  • File:line references for easy fixing
  • Severity ratings (CRITICAL/HIGH/MEDIUM/LOW)
  • Specific violation types
  • Fix recommendations with code examples

Files to Exclude

Skip: *Tests.swift, *Previews.swift, */Pods/*, */Carthage/*, */.build/*, */DerivedData/*, */scratch/*, */docs/*, */.claude/*, */.claude-plugin/*

Output Limits

If >50 issues in one category:

  • Show top 10 examples
  • Provide total count
  • List top 3 files with most issues

If >100 total issues:

  • Summarize by category
  • Show only CRITICAL/HIGH details
  • Always show: Severity counts, top 3 files by issue count

What You Check

1. GeometryReader in Stacks Without .frame() (CRITICAL)

Pattern: GeometryReader inside a VStack, HStack, or ZStack without an explicit .frame() on the GeometryReader Issue: GeometryReader expands to fill all available space, collapsing sibling views in stacks. This is the #1 SwiftUI layout surprise. Fix: Constrain GeometryReader with .frame(height:) or .frame(width:), or use onGeometryChange (iOS 18+) instead

2. UIDevice.current.orientation / UIScreen.main.bounds (CRITICAL)

Pattern: UIDevice.current.orientation, UIScreen.main.bounds, UIScreen.main.nativeBounds Issue: These APIs are deprecated/unreliable in SwiftUI. They don't account for multitasking, Stage Manager, or window resizing. They return stale values. Fix: Use GeometryReader, onGeometryChange, horizontalSizeClass, or ViewThatFits

3. UIRequiresFullScreen in Info.plist (CRITICAL)

Pattern: UIRequiresFullScreen set to true in Info.plist Issue: Disables all multitasking on iPad (Split View, Slide Over, Stage Manager). Apple rejects apps that use this unnecessarily. Fix: Remove UIRequiresFullScreen and support adaptive layouts with size classes

4. Size Class as Orientation Proxy (HIGH)

Pattern: horizontalSizeClass == .regular used to determine portrait vs landscape Issue: Size class doesn't map to orientation. iPad has .regular in both orientations. iPhone 15 Pro Max is .regular in landscape. This logic breaks on many devices. Fix: Use ViewThatFits for content-driven adaptation, or onGeometryChange for dimension-driven decisions

5. Conditional HStack/VStack (Identity Loss) (HIGH)

Pattern: if condition { VStack { ... } } else { HStack { ... } } Issue: Switching between stack types destroys and recreates all child views, losing scroll position, text field focus, and animation state. Fix: Use Layout protocol with AnyLayout, or ViewThatFits for automatic switching

6. Nested GeometryReaders (HIGH)

Pattern: Multiple GeometryReader blocks in the same file, especially nested Issue: Each GeometryReader proposes the full parent size to children. Nesting them creates confusing size propagation and usually indicates over-reliance on manual sizing. Fix: Use one GeometryReader at a high level and pass sizes down, or prefer onGeometryChange (iOS 18+)

7. Hardcoded Width/Height Breakpoints (MEDIUM)

Pattern: .width > 768, .width < 375, or similar numeric comparisons against geometry Issue: Hardcoded breakpoints break on new device sizes. iPhone and iPad dimensions change every year. Fix: Use horizontalSizeClass / verticalSizeClass for broad adaptation, ViewThatFits for content-driven decisions

8. Large Fixed Frames (300+ px) (MEDIUM)

Pattern: .frame(width: or .frame(height: with values of 300 or more Issue: Fixed frames larger than 300pt risk clipping on smaller devices (iPhone SE: 320pt wide) and waste space on larger devices. Fix: Use .frame(maxWidth:), relative sizing with containerRelativeFrame (iOS 17+), or flexible layouts

9. VStack/HStack + ForEach Without Lazy (MEDIUM)

Pattern: VStack { ForEach(...) } or HStack { ForEach(...) } (non-lazy) Issue: Non-lazy stacks instantiate ALL views upfront. With 100+ items, this causes launch lag and high memory usage. Fix: Use LazyVStack / LazyHStack inside a ScrollView for large collections

10. Missing .containerRelativeFrame (LOW)

Pattern: Custom GeometryReader usage solely for relative sizing (e.g., "50% of parent width") Issue: containerRelativeFrame (iOS 17+) handles relative sizing more cleanly than GeometryReader, with proper layout participation. Fix: Replace GeometryReader { geo in view.frame(width: geo.size.width * 0.5) } with .containerRelativeFrame(.horizontal) { w, _ in w * 0.5 }

Audit Process

Step 1: Find All SwiftUI Files

Use Glob to find Swift files, then Grep to find files containing:

  • import SwiftUI
  • View protocol conformance
  • body: computed property

Step 2: Search for Violations

Pattern 1: GeometryReader in stacks:

Grep: GeometryReader

Read matching files to check if GeometryReader is inside a stack without .frame().

Pattern 2: Deprecated screen/device APIs:

Grep: UIDevice\.current\.orientation
Grep: UIScreen\.main\.bounds
Grep: UIScreen\.main\.nativeBounds
Grep: UIScreen\.main\.scale

Pattern 3: UIRequiresFullScreen:

Grep: UIRequiresFullScreen

Search in *.plist files.

Pattern 4: Size class as orientation proxy:

Grep: horizontalSizeClass.*==.*\.regular
Grep: horizontalSizeClass.*==.*\.compact
Grep: verticalSizeClass.*==.*\.regular

Read matching files to check if size class is used to infer orientation rather than for genuine adaptation.

Pattern 5: Conditional stacks:

Grep: if.*\{[^}]*VStack
Grep: if.*\{[^}]*HStack

Read matching files to check for if/else switching between VStack and HStack.

Pattern 6: Nested GeometryReaders:

Grep: GeometryReader

Count occurrences per file. Flag files with 2+ GeometryReaders.

Pattern 7: Hardcoded breakpoints:

Grep: \.width\s*[<>]=?\s*\d{3}
Grep: \.height\s*[<>]=?\s*\d{3}
Grep: size\.width\s*[<>]=?\s*\d{3}

Pattern 8: Large fixed frames:

Grep: \.frame\(width:\s*\d{3,}
Grep: \.frame\(height:\s*\d{3,}

Flag values >= 300.

Pattern 9: Non-lazy ForEach in stacks:

Grep: VStack\s*\{[^}]*ForEach
Grep: HStack\s*\{[^}]*ForEach

Read matching files to verify ForEach is in a non-lazy container (not LazyVStack/LazyHStack).

Pattern 10: GeometryReader for relative sizing:

Grep: GeometryReader.*size\.width\s*\*
Grep: GeometryReader.*size\.height\s*\*

Flag when containerRelativeFrame would be simpler.

Step 3: Categorize by Severity

CRITICAL (Layout breaks or App Store rejection):

  • GeometryReader in stacks without frame
  • UIDevice/UIScreen deprecated APIs
  • UIRequiresFullScreen

HIGH (State loss or device-specific breakage):

  • Size class as orientation proxy
  • Conditional HStack/VStack identity loss
  • Nested GeometryReaders

MEDIUM (Performance or maintainability):

  • Hardcoded breakpoints
  • Large fixed frames
  • Non-lazy ForEach

LOW (Modernization opportunity):

  • Missing containerRelativeFrame

Output Format

markdown
# SwiftUI Layout Audit Results

## Summary
- **CRITICAL Issues**: [count] (Layout breaks/App Store rejection risk)
- **HIGH Issues**: [count] (State loss/device breakage)
- **MEDIUM Issues**: [count] (Performance/maintainability)
- **LOW Issues**: [count] (Modernization opportunities)

## Risk Score: [0-10]
(Each CRITICAL = +3 points, HIGH = +2 points, MEDIUM = +1 point, LOW = +0.5 points, cap at 10)

## CRITICAL Issues

### GeometryReader in Stack Without Frame
- `ContentView.swift:45` - GeometryReader inside VStack collapses sibling views
  - **Risk**: Layout completely broken — other views in stack disappear
  - **Fix**:
  ```swift
  // WRONG — GeometryReader takes all space
  VStack {
      Text("Title")
      GeometryReader { geo in
          // This pushes Text to zero height
      }
  }

  // CORRECT — Constrained GeometryReader
  VStack {
      Text("Title")
      GeometryReader { geo in
          // ...
      }
      .frame(height: 200)
  }

  // BETTER (iOS 18+) — No GeometryReader needed
  VStack {
      Text("Title")
      MyView()
          .onGeometryChange(for: CGSize.self) { proxy in
              proxy.size
          } action: { size in
              viewModel.updateSize(size)
          }
  }

[...continue for each issue found...]

Next Steps

  1. Fix CRITICAL issues immediately - Layout broken on some devices
  2. Replace deprecated APIs - UIScreen/UIDevice usage
  3. Test on iPad with multitasking - Split View, Stage Manager
  4. Test on smallest device (iPhone SE) and largest (iPad Pro 12.9")

## Audit Guidelines

1. Run all 10 pattern searches for comprehensive coverage
2. Provide file:line references to make issues easy to locate
3. Show exact fixes with code examples for each issue
4. Categorize by severity to help prioritize fixes
5. Calculate risk score to quantify overall safety level

## When Issues Found

If CRITICAL issues found:
- Emphasize layout breakage on specific device classes
- Recommend testing on iPad with Split View enabled
- Provide explicit code fixes with modern alternatives
- Calculate time to fix (usually 5-15 minutes per issue)

If NO issues found:
- Report "No SwiftUI layout violations detected"
- Recommend testing on multiple device sizes
- Suggest trying iPad multitasking modes

## False Positives (Not Issues)

- GeometryReader as the root view of a screen (no siblings to collapse)
- `UIScreen.main` used only for one-time setup (e.g., launch screen)
- `UIRequiresFullScreen` for camera-only or AR apps (legitimate use)
- Small fixed frames (< 100pt) for icons/badges
- `VStack { ForEach }` with < 20 items (lazy container overhead not worth it)
- Size class checks that genuinely adapt layout (not inferring orientation)

## Risk Score Calculation

- Each CRITICAL issue: +3 points
- Each HIGH issue: +2 points
- Each MEDIUM issue: +1 point
- Each LOW issue: +0.5 points
- Maximum score: 10

**Interpretation**:
- 0-2: Low risk, works across devices
- 3-5: Medium risk, may break on some devices
- 6-8: High risk, layout issues on iPad/multitasking
- 9-10: Critical risk, layout fundamentally broken

## Related

For SwiftUI layout patterns: `axiom-swiftui-layout` skill
For SwiftUI layout reference: `axiom-swiftui-layout-ref` skill
For SwiftUI containers: `axiom-swiftui-containers-ref` skill

Expand your agent's capabilities with these related and highly-rated skills.

Didn't find tool you were looking for?

Be as detailed as possible for better results