Agent skill
axiom-audit-swiftui-layout
Use when the user mentions SwiftUI layout review, adaptive layout issues, GeometryReader problems, or multi-device layout checking.
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 SwiftUIViewprotocol conformancebody: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
# 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
- Fix CRITICAL issues immediately - Layout broken on some devices
- Replace deprecated APIs - UIScreen/UIDevice usage
- Test on iPad with multitasking - Split View, Stage Manager
- 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
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
axiom-mapkit-diag
MapKit troubleshooting — annotations not appearing, region jumping, clustering not working, search failures, overlay rendering issues, user location problems
axiom-core-location
Use for Core Location implementation patterns - authorization strategy, monitoring strategy, accuracy selection, background location
axiom-energy
Use when app drains battery, device gets hot, users report energy issues, or auditing power consumption - systematic Power Profiler diagnosis, subsystem identification (CPU/GPU/Network/Location/Display), anti-pattern fixes for iOS/iPadOS
axiom-audit-storage
Use when the user mentions file storage issues, data loss, backup bloat, or asks to audit storage usage.
axiom-app-store-connect-ref
Use when navigating App Store Connect to find crash data, read TestFlight feedback, interpret metrics dashboards, or export diagnostic logs. Covers crash-free rates, dSYM symbolication, termination types, MetricKit.
axiom-testflight-triage
Use when ANY beta tester reports a crash, ANY crash appears in Organizer or App Store Connect, crash logs need symbolication, app was killed without crash report, or you need to triage TestFlight feedback
Didn't find tool you were looking for?