
axiom-swiftui-layout
Use when layouts need to adapt to different screen sizes, iPad multitasking, or iOS 26 free-form windows — decision trees for ViewThatFits vs AnyLayout vs onGeometryChange, size class limitations, and anti-patterns preventing device-based layout mistakes
- Installs
- 220
- Last 24h
- —
- Trending rank
- —
- Security
- Not audited
Install
Works with every agent the skills CLI supports.
npx skills add https://github.com/charleswiltgen/axiom --skill axiom-swiftui-layout- 01
Install
Run the command in your project. The CLI asks which agents to install for.
- 02
Describe the task
Ask your agent for something this skill covers. It loads axiom-swiftui-layout automatically when relevant.
- 03
Review the result
The agent follows the SKILL.md steps, scripts and references bundled with the skill.
SKILL.md
The instructions your agent reads when the skill is triggered.
SwiftUI Adaptive Layout
Overview
Discipline-enforcing skill for building layouts that respond to available space rather than device assumptions. Covers tool selection, size class limitations, iOS 26 free-form windows, and common anti-patterns.
Core principle: Your layout should work correctly if Apple ships a new device tomorrow, or if iPadOS adds a new multitasking mode next year. Respond to your container, not your assumptions about the device.
When to Use This Skill
- "How do I make this layout work on iPad and iPhone?"
- "Should I use GeometryReader or ViewThatFits?"
- "My layout breaks in Split View / Stage Manager"
- "Size classes aren't giving me what I need"
- "Designer wants different layout for portrait vs landscape"
- "Preparing app for iOS 26 window resizing"
Decision Tree
"I need my layout to adapt..."
│
├─ TO AVAILABLE SPACE (container-driven)
│ │
│ ├─ "Pick best-fitting variant"
│ │ → ViewThatFits
│ │
│ ├─ "Animated switch between H↔V"
│ │ → AnyLayout + condition
│ │
│ ├─ "Read size for calculations"
│ │ → onGeometryChange (iOS 16+)
│ │
│ └─ "Custom layout algorithm"
│ → Layout protocol
│
├─ TO PLATFORM TRAITS
│ │
│ ├─ "Compact vs Regular width"
│ │ → horizontalSizeClass (⚠️ iPad limitations)
│ │
│ ├─ "Accessibility text size"
│ │ → dynamicTypeSize.isAccessibilitySize
│ │
│ └─ "Platform differences"
│ → #if os() / Environment
│
└─ TO WINDOW SHAPE (aspect ratio)
│
├─ "Portrait vs Landscape semantics"
│ → Geometry + custom threshold
│
├─ "Auto show/hide columns"
│ → NavigationSplitView (automatic in iOS 26)
│
└─ "Window lifecycle"
→ @Environment(\.scenePhase)
Tool Selection
Quick Decision
Do you need a calculated value (width, height)?
├─ YES → onGeometryChange
└─ NO → Do you need animated transitions?
├─ YES → AnyLayout + condition
└─ NO → ViewThatFits
When to Use Each Tool
| I need to... | Use this | Not this |
|---|---|---|
| Pick between 2-3 layout variants | ViewThatFits | if size > X |
| Switch H↔V with animation | AnyLayout | Conditional HStack/VStack |
| Read container size | onGeometryChange | GeometryReader |
| Adapt to accessibility text | dynamicTypeSize | Fixed breakpoints |
| Detect compact width | horizontalSizeClass | UIDevice.idiom |
| Detect narrow window on iPad | Geometry + threshold | Size class alone |
| Hide/show sidebar | NavigationSplitView | Manual column logic |
| Custom layout algorithm | Layout protocol | Nested GeometryReaders |
Pattern 1: ViewThatFits
Use when: You have 2-3 layout variants and want SwiftUI to pick the first that fits.
ViewThatFits {
// First choice: horizontal
HStack {
Image(systemName: "star")
Text("Favorite")
Spacer()
Button("Add") { }
}
// Fallback: vertical
VStack {
HStack {
Image(systemName: "star")
Text("Favorite")
}
Button("Add") { }
}
}
Limitation: ViewThatFits doesn't expose which variant was chosen. If you need that state for other views, use AnyLayout instead.
Pattern 2: AnyLayout for Animated Switching
Use when: You need animated transitions between layouts, or need to know current layout state.
struct AdaptiveStack<Content: View>: View {
@Environment(\.horizontalSizeClass) var sizeClass
let content: Content
var layout: AnyLayout {
sizeClass == .compact
? AnyLayout(VStackLayout(spacing: 12))
: AnyLayout(HStackLayout(spacing: 20))
}
var body: some View {
layout {
content
}
.animation(.default, value: sizeClass)
}
}
For Dynamic Type:
@Environment(\.dynamicTypeSize) var dynamicTypeSize
var layout: AnyLayout {
dynamicTypeSize.isAccessibilitySize
? AnyLayout(VStackLayout())
: AnyLayout(HStackLayout())
}
Pattern 3: onGeometryChange (Preferred for Geometry)
Use when: You need actual dimensions for calculations. Preferred over GeometryReader.
struct ResponsiveGrid: View {
@State private var columnCount = 2
var body: some View {
LazyVGrid(columns: Array(repeating: GridItem(.flexible()), count: columnCount)) {
ForEach(items) { item in
ItemView(item: item)
}
}
.onGeometryChange(for: Int.self) { proxy in
max(1, Int(proxy.size.width / 150))
} action: { newCount in
columnCount = newCount
}
}
}
For aspect ratio detection (iPad "orientation"):
struct WindowShapeReader: View {
@State private var isWide = true
var body: some View {
content
.onGeometryChange(for: Bool.self) { proxy in
proxy.size.width > proxy.size.height * 1.2
} action: { newValue in
isWide = newValue
}
}
}
Pattern 4: GeometryReader (When Necessary)
Use when: You need geometry AND are on iOS 15 or earlier, OR need geometry during layout phase (not just as side effect).
// ✅ CORRECT: Constrained GeometryReader
VStack {
GeometryReader { geo in
Text("Width: \(geo.size.width)")
}
.frame(height: 44) // MUST constrain!
Button("Next") { }
}
// ❌ WRONG: Unconstrained (greedy)
VStack {
GeometryReader { geo in
Text("Width: \(geo.size.width)")
}
// Takes all available space, crushes siblings
Button("Next") { }
}
Size Class Truth Table (iPad)
| Configuration | Horizontal | Vertical |
|---|---|---|
| Full screen portrait | .regular | .regular |
| Full screen landscape | .regular | .regular |
| 70% Split View | .regular | .regular |
| 50% Split View | .regular | .regular |
| 33% Split View | .compact | .regular |
| Slide Over | .compact | .regular |
| With keyboard | (unchanged) | (unchanged) |
Key insight: Size class only goes .compact on iPad at ~33% width or Slide Over. For finer control, use geometry.
iOS 26 Free-Form Windows
What Changed
| Before iOS 26 | iOS 26+ |
|---|---|
| Fixed Split View sizes | Free-form drag-to-resize |
UIRequiresFullScreen allowed | Deprecated |
| No menu bar on iPad | Menu bar via .commands |
| Manual column visibility | NavigationSplitView auto-adapts |
Apple's Guideline
"Resizing an app should not permanently alter its layout. Be opportunistic about reverting back to the starting state whenever possible."
Translation: Don't save layout state based on window size. When window returns to original size, layout should too.
NavigationSplitView Auto-Adaptation
// iOS 26: Columns automatically show/hide
NavigationSplitView {
Sidebar()
} content: {
ContentList()
} detail: {
DetailView()
}
// No manual columnVisibility management needed
Migration Checklist
- Remove
UIRequiresFullScreenfrom Info.plist - Test at arbitrary window sizes (not just 33/50/66%)
- Verify layout doesn't "stick" after resize
- Add menu bar commands for common actions
- Test Window Controls don't overlap toolbar items
Anti-Patterns
❌ Device Orientation Observer
// ❌ WRONG: Reports device, not window
NotificationCenter.default.addObserver(
forName: UIDevice.orientationDidChangeNotification, ...
)
let orientation = UIDevice.current.orientation
if orientation.isLandscape { ... }
Why it fails: Reports physical device orientation, not window shape. Wrong in Split View, Stage Manager, iOS 26.
Fix: Use onGeometryChange to read actual window dimensions.
❌ Screen Bounds
// ❌ WRONG: Returns full screen, not your window
let width = UIScreen.main.bounds.width
if width > 700 { useWideLayout() }
Why it fails: In multitasking, your app may only have 40% of the screen.
Fix: Read your view's actual container size.
❌ Device Model Checks
// ❌ WRONG: Breaks on new devices, wrong in multitasking
if UIDevice.current.userInterfaceIdiom == .pad {
useWideLayout()
}
Why it fails: iPad in 1/3 Split View is narrower than iPhone 14 Pro Max landscape.
Fix: Respond to available space, not device identity.
❌ Unconstrained GeometryReader
// ❌ WRONG: GeometryReader is greedy
VStack {
GeometryReader { geo in
Text("Size: \(geo.size)")
}
Button("Next") { } // Crushed
}
Fix: Constrain with .frame() or use onGeometryChange.
❌ Size Class as Orientation Proxy
// ❌ WRONG: iPad is .regular in both orientations
var isLandscape: Bool {
horizontalSizeClass == .regular // Always true on iPad!
}
Fix: Calculate from actual geometry if you need aspect ratio.
Pressure Scenarios
"Designer wants iPhone-specific layout"
Temptation: if UIDevice.current.userInterfaceIdiom == .phone
Response: "I'll implement these as 'compact' and 'regular' layouts that switch based on available space. The iPhone layout will appear on iPad when the window is narrow. This future-proofs us for Stage Manager and iOS 26."
"Just use GeometryReader, it's fine"
Temptation: Wrap everything in GeometryReader.
Response: "GeometryReader has known layout side effects — it expands greedily. onGeometryChange reads the same data without affecting layout. It's backported to iOS 16."
"Size classes worked before"
Temptation: Force everything through size class.
Response: "Size classes are coarse. iPad is .regular in both orientations. I'll use size class for broad categories and geometry for precise thresholds."
"We don't support iPad multitasking"
Temptation: UIRequiresFullScreen = true
Response: "Apple deprecated full-screen-only in iOS 26. Even without active Split View support, the app can't break when resized. Space-based layout costs the same."
Resources
WWDC: 2025-208, 2024-10074, 2022-10056
Skills: axiom-swiftui-layout-ref, axiom-swiftui-debugging, axiom-liquid-glass
FAQ
What does the axiom-swiftui-layout skill do?
Use when layouts need to adapt to different screen sizes, iPad multitasking, or iOS 26 free-form windows — decision trees for ViewThatFits vs AnyLayout vs onGeometryChange, size class limitations, and anti-patterns preventing device-based layout mistakes
How do I install axiom-swiftui-layout?
Run npx skills add https://github.com/charleswiltgen/axiom --skill axiom-swiftui-layout in your project. The skills CLI detects the agents you use and copies the skill into the right folder. For Claude Code you can also place the skill folder at ~/.claude/skills/axiom-swiftui-layout/ (personal) or .claude/skills/axiom-swiftui-layout/ (project).
Which AI agents can use axiom-swiftui-layout?
Any agent that supports the open Agent Skills format (a folder with a SKILL.md file), including Claude Code, Codex, Cursor, GitHub Copilot, Gemini CLI, Windsurf, Cline, OpenCode and more.
Is axiom-swiftui-layout safe to install?
No partner security audit is available yet. Skills can run commands and scripts, so read the SKILL.md and any bundled files before installing.
Who maintains axiom-swiftui-layout?
It is published by charleswiltgen in charleswiltgen/axiom. It has 220 recorded installs.
More from charleswiltgen/axiom
axiom-vision
charleswiltgen/axiom
Use when implementing ANY computer vision feature — image analysis, pose detection, person segmentation, subject lifting, text recognition, barcode scanning.
axiom-swiftui
charleswiltgen/axiom
Use when building, fixing, or improving ANY SwiftUI UI — views, navigation, layout, animations, gestures, debugging, iOS 26 features, iPhone Duo, view-level performance, feature architecture.
axiom-networking
charleswiltgen/axiom
Use when implementing or debugging ANY network connection, API call, or socket in an Apple app. Covers URLSession, Network.framework, NetworkConnection, connection diagnostics.
axiom-apple-docs
charleswiltgen/axiom
Use when you need Apple's own documentation or a Swift compiler diagnostic explained rather than recalled.
axiom-shipping
charleswiltgen/axiom
Use when preparing an app for App Store submission, handling rejections or appeals, managing App Store Connect and Xcode Cloud, or triaging TestFlight/Sentry crash corpora. Covers checklists, metadata, privacy manifests, age ratings, export compliance.
axiom-xcode-mcp
charleswiltgen/axiom
Use when connecting to Xcode via MCP, using xcrun mcpbridge or the headless mcp-server, or working with ANY Xcode MCP tool (XcodeRead, BuildProject, RunSomeTests, RenderPreview). Covers setup, tool reference, workflows, troubleshooting.
Popular mobile skills
design-mobile-apps
designed-by-ai/skills
Design mobile app screens with Sleek, edit Sleek projects, and implement their designs in React Native or HTML.
ios-design
uizze.sh
Design and review iOS UI/UX for iPhone apps in SwiftUI, UIKit, or React Native. Use for mobile app design, screen redesigns, navigation flows, typography, forms, keyboard and safe-area layout, and accessibility checks. Apply Apple Human Interface Guidelines and consult real product references through the optional Uizze connection.
vercel-react-native-skills
vercel-labs/agent-skills
React Native and Expo best practices for building performant mobile apps. Use when building React Native components, optimizing list performance, implementing animations, or working with native modules. Triggers on tasks involving React Native, Expo, mobile performance, or native platform APIs.
animate-expo
emilkowalski/skills
Build animations in React Native and Expo, making the decisions in the order that determines whether they feel right — should it animate, which thread it runs on, which properties, spring or timing, how the gesture hands off, how it degrades. Writes the implementation with Reanimated, Gesture Handler, Expo Router and expo-haptics. Use when animating anything in an Expo app, adding gestures, sheets, screen transitions, press feedback or haptics, or fixing motion that stutters on device. For web animation use animate.
xcode-project-setup
firebase/agent-skills
Safely modifies Xcode projects (.pbxproj) to add Swift Packages and link files. Use this skill whenever an iOS project needs dependencies installed (e.g. Firebase, Alamofire).
firebase-remote-config-basics
firebase/agent-skills
Manages Firebase Remote Config templates, feature flags, loading strategies, and SDKs (Android, iOS). Use when downloading/deploying remoteconfig JSON templates, managing version history/feature flags, setting in-app defaults, fetchAndActivate(), real-time listeners, or SDK setup. Don't use for Firebase Hosting, Auth, Firestore, Data Connect, Crashlytics, or App Hosting.