Communitygithub.com

ComeOnOliver/skillshub

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

skillshub란 무엇인가요?

skillshub is a Claude Code agent skill that 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.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/ComeOnOliver/skillshub/tree/HEAD/skills/CharlesWiltgen/Axiom/axiom-swiftui-layout

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

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 thisNot this
Pick between 2-3 layout variantsViewThatFitsif size > X
Switch H↔V with animationAnyLayoutConditional HStack/VStack
Read container sizeonGeometryChangeGeometryReader
Adapt to accessibility textdynamicTypeSizeFixed breakpoints
Detect compact widthhorizontalSizeClassUIDevice.idiom
Detect narrow window on iPadGeometry + thresholdSize class alone
Hide/show sidebarNavigationSplitViewManual column logic
Custom layout algorithmLayout protocolNested 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)

ConfigurationHorizontalVertical
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 26iOS 26+
Fixed Split View sizesFree-form drag-to-resize
UIRequiresFullScreen allowedDeprecated
No menu bar on iPadMenu bar via .commands
Manual column visibilityNavigationSplitView 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 UIRequiresFullScreen from 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

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

ComeOnOliver/skillshub

Create stunning, animation-rich HTML presentations from scratch or by converting PowerPoint files. Use when the user wants to build a presentation, convert a PPT/PPTX to web, or create slides for a talk/pitch. Helps non-designers discover their aesthetic through visual exploration rather than abstract choices.

ComeOnOliver/skillshub

Next.js 16+ 和 Turbopack — 增量打包、文件系统缓存、开发速度,以及何时使用 Turbopack 与 webpack。

ComeOnOliver/skillshub

SwiftUI architecture patterns, state management with @Observable, view composition, navigation, performance optimization, and modern iOS/macOS UI best practices.

ComeOnOliver/skillshub

Next.js 15 애플리케이션을 위한 프론트엔드 개발 가이드라인. React 19, TypeScript, Shadcn/ui, Tailwind CSS를 사용한 모던 패턴. Server Components, Client Components, App Router, 파일 구조, Shadcn/ui 컴포넌트, 성능 최적화, TypeScript 모범 사례 포함. 컴포넌트, 페이지, 기능 생성, 데이터 페칭, 스타일링, 라우팅, 프론트엔드 코드 작업 시 사용.

ComeOnOliver/skillshub

Provides Tamagui patterns for config v4, compiler optimization, styled context, and cross-platform styling. Must use when working with Tamagui projects (tamagui.config.ts, @tamagui imports).

ComeOnOliver/skillshub

Create distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.

ComeOnOliver/skillshub

Create high-converting, visually distinctive landing pages. Use when building marketing pages, product launches, SaaS homepages, or any single-page conversion-focused website. Guides section-by-section composition with anti-AI-slop principles.

ComeOnOliver/skillshub

Auto-loaded context for Portfolio Buddy 2 development. Use for ANY task involving: React 19 development, TypeScript, portfolio analysis features, metrics calculations, trading strategy comparison, or working with the Portfolio Buddy 2 codebase. Contains tech stack, known issues, and architectural constraints.

ComeOnOliver/skillshub

Next.js development tooling via MCP. Inspect routes, components, build info, and debug Next.js apps. Use when working on Next.js applications, debugging routing, or inspecting app structure. NOT for general React or non-Next.js projects.

ComeOnOliver/skillshub

Implement UI using Shadcn MCP (atoms/theme) and 21st.dev MCP (complex sections). Use when adding buttons, layouts, or generating landing pages.

ComeOnOliver/skillshub

A conceptual skill for building an API client in Next.js that handles JWT tokens

ComeOnOliver/skillshub

Best practices and patterns for Next.js App Router, Server Actions, and Routing in this project.

ComeOnOliver/skillshub

The Design System, Theme, and UX rules for the Physical AI Hub.

ComeOnOliver/skillshub

Comprehensive frontend development skill for building modern, performant web applications using ReactJS, NextJS, TypeScript, Tailwind CSS. Includes component scaffolding, performance optimization, bundle analysis, and UI best practices. Use when developing frontend features, optimizing performance, implementing UI/UX designs, managing state, or reviewing frontend code.

ComeOnOliver/skillshub

Build Next.js 16 applications with correct patterns and distinctive design. Use when creating pages, layouts, dynamic routes, upgrading from Next.js 15, or implementing proxy.ts. Covers breaking changes (async params/searchParams, Turbopack, cacheComponents) and frontend aesthetics. NOT when building non-React or backend-only applications.

ComeOnOliver/skillshub

Next.js 16+ uses App Router with Server Components by default. Client Components are only used when interactivity is needed (hooks, event handlers, browser APIs).

ComeOnOliver/skillshub

All TypeScript types are defined in `frontend/types/index.ts`. Types match backend API response structure and provide type safety across the frontend application.

ComeOnOliver/skillshub

Use when working with Next.js App Router tasks - creating pages in /app/, setting up dynamic routes ([id]/page.tsx), implementing nested layouts/templates (layout.tsx), optimizing Server/Client components, or building ERP role-based pages (admin/teacher/student dashboards). Auto-use for all /app/ directory operations, dynamic routing, and App Router-specific features.

ComeOnOliver/skillshub

Apple-inspired clean, minimal, premium UI design. Use when building modern interfaces requiring exceptional UX, clean aesthetics, or Apple-like polish. Triggers on: clean UI, modern design, Apple style, minimal, premium, user-friendly, UX.

ComeOnOliver/skillshub

Build interfaces that resonate, not just render. This skill enforces bold, intentional

관련 스킬