[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6E6S2V2X2BAVR2DC00H2SPH":3,"skill-same:01M6E6S2V2X2BAVR2DC00H2SPH":63},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":33,"linkOnly":34,"markdownContent":35,"markdownBody":36,"frontMatter":37,"refName":40,"commitSha":41,"skillDirectory":42,"forksCount":43,"repositoryHtmlUrl":44,"repositoryHomepageUrl":45,"ownerHtmlUrl":46,"ownerBlogUrl":6,"ownerDescription":47,"updatedAt":48,"install":49,"resources":62},"01M6E6S2V2X2BAVR2DC00H2SPH","axiom-swiftui-layout-ref",null,"Reference — Complete SwiftUI adaptive layout API guide covering ViewThatFits, AnyLayout, Layout protocol, onGeometryChange, GeometryReader, size classes, and iOS 26 window APIs","ComeOnOliver\u002Fskillshub","ComeOnOliver","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F43215937?v=4","01M6E5JNFECJAAVFE0W7SMWWD2","Haoran Shu","01M6E5JNNDQS7AJZDYFQ8TPZCK",66,"MIT","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002FCharlesWiltgen\u002FAxiom\u002Faxiom-swiftui-layout-ref\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[24,25,26,27,28,29,30,31,32],"swiftui","layout","axiom swiftui layout ref","protocol","ios","api","guide","reference","complete","2026-11-10 10:12:30",false,"---\nname: axiom-swiftui-layout-ref\ndescription: Reference — Complete SwiftUI adaptive layout API guide covering ViewThatFits, AnyLayout, Layout protocol, onGeometryChange, GeometryReader, size classes, and iOS 26 window APIs\nlicense: MIT\nmetadata:\n  version: \"1.0.0\"\n---\n\n# SwiftUI Layout API Reference\n\nComprehensive API reference for SwiftUI adaptive layout tools. For decision guidance and anti-patterns, see the `axiom-swiftui-layout` skill.\n\n## Overview\n\nThis reference covers all SwiftUI layout APIs for building adaptive interfaces:\n\n- **ViewThatFits** — Automatic variant selection (iOS 16+)\n- **AnyLayout** — Type-erased animated layout switching (iOS 16+)\n- **Layout Protocol** — Custom layout algorithms (iOS 16+)\n- **onGeometryChange** — Efficient geometry reading (iOS 16+ backported)\n- **GeometryReader** — Layout-phase geometry access (iOS 13+)\n- **Safe Area Padding** — .safeAreaPadding() vs .padding() (iOS 17+)\n- **Size Classes** — Trait-based adaptation\n- **iOS 26 Window APIs** — Free-form windows, menu bar, resize anchors\n\n---\n\n## ViewThatFits\n\nEvaluates child views in order and displays the first one that fits in the available space.\n\n### Basic Usage\n\n```swift\nViewThatFits {\n    \u002F\u002F First choice\n    HStack {\n        icon\n        title\n        Spacer()\n        button\n    }\n\n    \u002F\u002F Second choice\n    HStack {\n        icon\n        title\n        button\n    }\n\n    \u002F\u002F Fallback\n    VStack {\n        HStack { icon; title }\n        button\n    }\n}\n```\n\n### With Axis Constraint\n\n```swift\n\u002F\u002F Only consider horizontal fit\nViewThatFits(in: .horizontal) {\n    wideVersion\n    narrowVersion\n}\n\n\u002F\u002F Only consider vertical fit\nViewThatFits(in: .vertical) {\n    tallVersion\n    shortVersion\n}\n```\n\n### How It Works\n\n1. Applies `fixedSize()` to each child\n2. Measures ideal size against available space\n3. Returns first child that fits\n4. Falls back to last child if none fit\n\n### Limitations\n\n- Does not expose which variant was selected\n- Cannot animate between variants (use AnyLayout instead)\n- Measures all variants (performance consideration for complex views)\n\n---\n\n## AnyLayout\n\nType-erased layout container enabling animated transitions between layouts.\n\n### Basic Usage\n\n```swift\nstruct AdaptiveView: View {\n    @Environment(\\.horizontalSizeClass) var sizeClass\n\n    var layout: AnyLayout {\n        sizeClass == .compact\n            ? AnyLayout(VStackLayout(spacing: 12))\n            : AnyLayout(HStackLayout(spacing: 20))\n    }\n\n    var body: some View {\n        layout {\n            ForEach(items) { item in\n                ItemView(item: item)\n            }\n        }\n        .animation(.default, value: sizeClass)\n    }\n}\n```\n\n### Available Layout Types\n\n```swift\nAnyLayout(HStackLayout(alignment: .top, spacing: 10))\nAnyLayout(VStackLayout(alignment: .leading, spacing: 8))\nAnyLayout(ZStackLayout(alignment: .center))\nAnyLayout(GridLayout(alignment: .leading, horizontalSpacing: 10, verticalSpacing: 10))\n```\n\n### Custom Conditions\n\n```swift\n\u002F\u002F Based on Dynamic Type\n@Environment(\\.dynamicTypeSize) var typeSize\n\nvar layout: AnyLayout {\n    typeSize.isAccessibilitySize\n        ? AnyLayout(VStackLayout())\n        : AnyLayout(HStackLayout())\n}\n\n\u002F\u002F Based on geometry\n@State private var isWide = true\n\nvar layout: AnyLayout {\n    isWide\n        ? AnyLayout(HStackLayout())\n        : AnyLayout(VStackLayout())\n}\n```\n\n### Why Use Over Conditional Views\n\n```swift\n\u002F\u002F ❌ Loses view identity, no animation\nif isCompact {\n    VStack { content }\n} else {\n    HStack { content }\n}\n\n\u002F\u002F ✅ Preserves identity, smooth animation\nlet layout = isCompact ? AnyLayout(VStackLayout()) : AnyLayout(HStackLayout())\nlayout { content }\n```\n\n---\n\n## Layout Protocol\n\nCreate custom layout containers with full control over positioning.\n\n### Basic Custom Layout\n\n```swift\nstruct FlowLayout: Layout {\n    var spacing: CGFloat = 8\n\n    func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {\n        let sizes = subviews.map { $0.sizeThatFits(.unspecified) }\n        return calculateSize(for: sizes, in: proposal.width ?? .infinity)\n    }\n\n    func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {\n        var point = bounds.origin\n        var lineHeight: CGFloat = 0\n\n        for subview in subviews {\n            let size = subview.sizeThatFits(.unspecified)\n\n            if point.x + size.width > bounds.maxX {\n                point.x = bounds.origin.x\n                point.y += lineHeight + spacing\n                lineHeight = 0\n            }\n\n            subview.place(at: point, proposal: .unspecified)\n            point.x += size.width + spacing\n            lineHeight = max(lineHeight, size.height)\n        }\n    }\n}\n\n\u002F\u002F Usage\nFlowLayout(spacing: 12) {\n    ForEach(tags) { tag in\n        TagView(tag: tag)\n    }\n}\n```\n\n### With Cache\n\n```swift\nstruct CachedLayout: Layout {\n    struct CacheData {\n        var sizes: [CGSize] = []\n    }\n\n    func makeCache(subviews: Subviews) -> CacheData {\n        CacheData(sizes: subviews.map { $0.sizeThatFits(.unspecified) })\n    }\n\n    func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout CacheData) -> CGSize {\n        \u002F\u002F Use cache.sizes instead of measuring again\n    }\n}\n```\n\n### Layout Values\n\n```swift\n\u002F\u002F Define custom layout value\nstruct Rank: LayoutValueKey {\n    static let defaultValue: Int = 0\n}\n\nextension View {\n    func rank(_ value: Int) -> some View {\n        layoutValue(key: Rank.self, value: value)\n    }\n}\n\n\u002F\u002F Read in layout\nfunc placeSubviews(...) {\n    let sorted = subviews.sorted { $0[Rank.self] \u003C $1[Rank.self] }\n}\n```\n\n---\n\n## onGeometryChange\n\nEfficient geometry reading without layout side effects. Backported to iOS 16+.\n\n### Basic Usage\n\n```swift\n@State private var size: CGSize = .zero\n\nvar body: some View {\n    content\n        .onGeometryChange(for: CGSize.self) { proxy in\n            proxy.size\n        } action: { newSize in\n            size = newSize\n        }\n}\n```\n\n### Reading Specific Values\n\n```swift\n\u002F\u002F Width only\n.onGeometryChange(for: CGFloat.self) { proxy in\n    proxy.size.width\n} action: { width in\n    columnCount = max(1, Int(width \u002F 150))\n}\n\n\u002F\u002F Frame in coordinate space\n.onGeometryChange(for: CGRect.self) { proxy in\n    proxy.frame(in: .global)\n} action: { frame in\n    globalFrame = frame\n}\n\n\u002F\u002F Aspect ratio\n.onGeometryChange(for: Bool.self) { proxy in\n    proxy.size.width > proxy.size.height\n} action: { isWide in\n    self.isWide = isWide\n}\n```\n\n### Coordinate Spaces\n\n```swift\n\u002F\u002F Named coordinate space\nScrollView {\n    content\n        .onGeometryChange(for: CGFloat.self) { proxy in\n            proxy.frame(in: .named(\"scroll\")).minY\n        } action: { offset in\n            scrollOffset = offset\n        }\n}\n.coordinateSpace(name: \"scroll\")\n```\n\n### Comparison with GeometryReader\n\n| Aspect | onGeometryChange | GeometryReader |\n|--------|------------------|----------------|\n| Layout impact | None | Greedy (fills space) |\n| When evaluated | After layout | During layout |\n| Use case | Side effects | Layout calculations |\n| iOS version | 16+ (backported) | 13+ |\n\n---\n\n## GeometryReader\n\nProvides geometry information during layout phase. Use sparingly due to greedy sizing.\n\n### Basic Usage (Constrained)\n\n```swift\n\u002F\u002F ✅ Always constrain GeometryReader\nGeometryReader { proxy in\n    let width = proxy.size.width\n    HStack(spacing: 0) {\n        Rectangle().frame(width: width * 0.3)\n        Rectangle().frame(width: width * 0.7)\n    }\n}\n.frame(height: 100)  \u002F\u002F Required constraint\n```\n\n### GeometryProxy Properties\n\n```swift\nGeometryReader { proxy in\n    \u002F\u002F Container size\n    let size = proxy.size  \u002F\u002F CGSize\n\n    \u002F\u002F Safe area insets\n    let insets = proxy.safeAreaInsets  \u002F\u002F EdgeInsets\n\n    \u002F\u002F Frame in coordinate space\n    let globalFrame = proxy.frame(in: .global)\n    let localFrame = proxy.frame(in: .local)\n    let namedFrame = proxy.frame(in: .named(\"container\"))\n}\n```\n\n### Common Patterns\n\n```swift\n\u002F\u002F Proportional sizing\nGeometryReader { geo in\n    VStack {\n        header.frame(height: geo.size.height * 0.2)\n        content.frame(height: geo.size.height * 0.8)\n    }\n}\n\n\u002F\u002F Centering with offset\nGeometryReader { geo in\n    content\n        .position(x: geo.size.width \u002F 2, y: geo.size.height \u002F 2)\n}\n```\n\n### Avoiding Common Mistakes\n\n```swift\n\u002F\u002F ❌ Unconstrained in VStack\nVStack {\n    GeometryReader { ... }  \u002F\u002F Takes ALL space\n    Button(\"Next\") { }       \u002F\u002F Invisible\n}\n\n\u002F\u002F ✅ Constrained\nVStack {\n    GeometryReader { ... }\n        .frame(height: 200)\n    Button(\"Next\") { }\n}\n\n\u002F\u002F ❌ Causing layout loops\nGeometryReader { geo in\n    content\n        .frame(width: geo.size.width)  \u002F\u002F Can cause infinite loop\n}\n```\n\n---\n\n## Safe Area Padding\n\nSwiftUI provides two primary approaches for handling spacing around content: `.padding()` and `.safeAreaPadding()`. Understanding when to use each is critical for proper layout on devices with safe areas (notch, Dynamic Island, home indicator).\n\n### The Critical Difference\n\n```swift\n\u002F\u002F ❌ WRONG - Ignores safe areas, content hits notch\u002Fhome indicator\nScrollView {\n    content\n}\n.padding(.horizontal, 20)\n\n\u002F\u002F ✅ CORRECT - Respects safe areas, adds padding beyond them\nScrollView {\n    content\n}\n.safeAreaPadding(.horizontal, 20)\n```\n\n**Key insight**: `.padding()` adds fixed spacing from the view's edges. `.safeAreaPadding()` adds spacing beyond the safe area insets.\n\n### When to Use Each\n\n#### Use `.padding()` when\n\n- Adding spacing between sibling views within a container\n- Creating internal spacing that should be consistent everywhere\n- Working with views that already respect safe areas (like List, Form)\n- Adding decorative spacing on macOS (no safe area concerns)\n\n```swift\nVStack(spacing: 0) {\n    header\n        .padding(.horizontal, 16)  \u002F\u002F ✅ Internal spacing\n\n    Divider()\n\n    content\n        .padding(.horizontal, 16)  \u002F\u002F ✅ Internal spacing\n}\n```\n\n#### Use `.safeAreaPadding()` when (iOS 17+)\n\n- Adding margin to full-width content that extends to screen edges\n- Implementing edge-to-edge scrolling with proper insets\n- Creating custom containers that need safe area awareness\n- Working with Liquid Glass or full-screen materials\n\n```swift\n\u002F\u002F ✅ Edge-to-edge list with custom padding\nList(items) { item in\n    ItemRow(item)\n}\n.listStyle(.plain)\n.safeAreaPadding(.horizontal, 20)  \u002F\u002F Adds 20pt beyond safe areas\n\n\u002F\u002F ✅ Full-screen content with proper margins\nZStack {\n    Color.blue.ignoresSafeArea()\n\n    VStack {\n        content\n    }\n    .safeAreaPadding(.all, 16)  \u002F\u002F Respects notch, home indicator\n}\n```\n\n### Platform Availability\n\n**iOS 17+, iPadOS 17+, macOS 14+, axiom-visionOS 1.0+**\n\nFor earlier iOS versions, use manual safe area handling:\n\n```swift\n\u002F\u002F iOS 13-16 fallback\nGeometryReader { geo in\n    content\n        .padding(.horizontal, 20 + geo.safeAreaInsets.leading)\n}\n```\n\nOr conditional compilation:\n\n```swift\nif #available(iOS 17, *) {\n    content.safeAreaPadding(.horizontal, 20)\n} else {\n    content.padding(.horizontal, 20)\n        .padding(.leading, safeAreaInsets.leading)\n}\n```\n\n### Edge-Specific Usage\n\n```swift\n\u002F\u002F Top only (below status bar\u002Fnotch)\n.safeAreaPadding(.top, 8)\n\n\u002F\u002F Bottom only (above home indicator)\n.safeAreaPadding(.bottom, 16)\n\n\u002F\u002F Horizontal (left\u002Fright of safe areas)\n.safeAreaPadding(.horizontal, 20)\n\n\u002F\u002F All edges\n.safeAreaPadding(.all, 16)\n\n\u002F\u002F Individual edges\n.safeAreaPadding(EdgeInsets(top: 8, leading: 20, bottom: 16, trailing: 20))\n```\n\n### Common Patterns\n\n#### Edge-to-Edge ScrollView\n\n```swift\nScrollView {\n    LazyVStack(spacing: 12) {\n        ForEach(items) { item in\n            ItemCard(item)\n        }\n    }\n}\n.safeAreaPadding(.horizontal, 16)  \u002F\u002F Content inset from edges + safe areas\n.safeAreaPadding(.vertical, 8)\n```\n\n#### Full-Screen Background with Safe Content\n\n```swift\nZStack {\n    \u002F\u002F Background extends edge-to-edge\n    LinearGradient(...)\n        .ignoresSafeArea()\n\n    \u002F\u002F Content respects safe areas + custom padding\n    VStack {\n        header\n        Spacer()\n        content\n        Spacer()\n        footer\n    }\n    .safeAreaPadding(.all, 20)\n}\n```\n\n#### Nested Padding (Combined Approach)\n\n```swift\n\u002F\u002F Outer: Safe area padding for device insets\nVStack(spacing: 0) {\n    content\n}\n.safeAreaPadding(.horizontal, 16)  \u002F\u002F Beyond safe areas\n\n\u002F\u002F Inner: Regular padding for internal spacing\nVStack {\n    Text(\"Title\")\n        .padding(.bottom, 8)  \u002F\u002F Internal spacing\n    Text(\"Subtitle\")\n}\n```\n\n### Decision Tree\n\n```\nDoes your content extend to screen edges?\n├─ YES → Use .safeAreaPadding()\n│   ├─ Is it scrollable? → .safeAreaPadding(.horizontal\u002F.vertical)\n│   └─ Is it full-screen? → .safeAreaPadding(.all)\n│\n└─ NO (contained within a safe container like List\u002FForm)\n    └─ Use .padding() for internal spacing\n```\n\n### Visual Debugging\n\n```swift\n\u002F\u002F Visualize safe area padding (iOS 17+)\ncontent\n    .safeAreaPadding(.horizontal, 20)\n    .background(.red.opacity(0.2))  \u002F\u002F Shows padding area\n    .border(.blue)  \u002F\u002F Shows content bounds\n```\n\n### Migration from Manual Safe Area Handling\n\n```swift\n\u002F\u002F ❌ OLD: Manual calculation (iOS 13-16)\nGeometryReader { geo in\n    content\n        .padding(.top, geo.safeAreaInsets.top + 16)\n        .padding(.bottom, geo.safeAreaInsets.bottom + 16)\n        .padding(.horizontal, 20)\n}\n\n\u002F\u002F ✅ NEW: .safeAreaPadding() (iOS 17+)\ncontent\n    .safeAreaPadding(.vertical, 16)\n    .safeAreaPadding(.horizontal, 20)\n```\n\n### Related APIs\n\n**`.safeAreaInset(edge:)`** - Adds persistent content that shrinks the safe area:\n```swift\nScrollView {\n    content\n}\n.safeAreaInset(edge: .bottom) {\n    \u002F\u002F This REDUCES the safe area, content scrolls under it\n    toolbarButtons\n        .padding()\n        .background(.ultraThinMaterial)\n}\n```\n\n**`.ignoresSafeArea()`** - Opts out of safe area completely:\n```swift\nColor.blue\n    .ignoresSafeArea()  \u002F\u002F Extends to absolute screen edges\n```\n\n### Why It Matters\n\n**Before iOS 17**: Developers had to manually calculate safe area insets with GeometryReader, leading to:\n- Verbose code\n- Performance overhead (GeometryReader forces extra layout pass)\n- Easy mistakes (forgetting to check all edges)\n\n**iOS 17+**: `.safeAreaPadding()` provides:\n- Declarative API (matches SwiftUI philosophy)\n- Automatic safe area awareness\n- Better performance (no extra layout passes)\n- Type-safe edge specification\n\n**Real-world impact**: Using `.padding()` instead of `.safeAreaPadding()` on iPhone 15 Pro causes content to:\n- Hit the Dynamic Island (top)\n- Overlap the home indicator (bottom)\n- Get cut off by screen corners (rounded edges)\n\n---\n\n## Size Classes\n\nEnvironment values indicating horizontal and vertical size characteristics.\n\n### Reading Size Classes\n\n```swift\nstruct AdaptiveView: View {\n    @Environment(\\.horizontalSizeClass) var horizontalSizeClass\n    @Environment(\\.verticalSizeClass) var verticalSizeClass\n\n    var body: some View {\n        if horizontalSizeClass == .compact {\n            compactLayout\n        } else {\n            regularLayout\n        }\n    }\n}\n```\n\n### Size Class Values\n\n```swift\nenum UserInterfaceSizeClass {\n    case compact    \u002F\u002F Constrained space\n    case regular    \u002F\u002F Ample space\n}\n```\n\n### Platform Behavior\n\n**iPhone:**\n| Orientation | Horizontal | Vertical |\n|-------------|------------|----------|\n| Portrait | `.compact` | `.regular` |\n| Landscape (small) | `.compact` | `.compact` |\n| Landscape (Plus\u002FMax) | `.regular` | `.compact` |\n\n**iPad:**\n| Configuration | Horizontal | Vertical |\n|--------------|------------|----------|\n| Any full screen | `.regular` | `.regular` |\n| 70% Split View | `.regular` | `.regular` |\n| 50% Split View | `.regular` | `.regular` |\n| 33% Split View | `.compact` | `.regular` |\n| Slide Over | `.compact` | `.regular` |\n\n### Overriding Size Classes\n\n```swift\ncontent\n    .environment(\\.horizontalSizeClass, .compact)\n```\n\n---\n\n## Dynamic Type Size\n\nEnvironment value for user's preferred text size.\n\n### Reading Dynamic Type\n\n```swift\n@Environment(\\.dynamicTypeSize) var dynamicTypeSize\n\nvar body: some View {\n    if dynamicTypeSize.isAccessibilitySize {\n        accessibleLayout\n    } else {\n        standardLayout\n    }\n}\n```\n\n### Size Categories\n\n```swift\nenum DynamicTypeSize: Comparable {\n    case xSmall\n    case small\n    case medium\n    case large           \u002F\u002F Default\n    case xLarge\n    case xxLarge\n    case xxxLarge\n    case accessibility1  \u002F\u002F isAccessibilitySize = true\n    case accessibility2\n    case accessibility3\n    case accessibility4\n    case accessibility5\n}\n```\n\n### Scaled Metric\n\n```swift\n@ScaledMetric var iconSize: CGFloat = 24\n@ScaledMetric(relativeTo: .largeTitle) var headerSize: CGFloat = 44\n\nImage(systemName: \"star\")\n    .frame(width: iconSize, height: iconSize)\n```\n\n---\n\n## iOS 26 Window APIs\n\n### Window Resize Anchor\n\n```swift\nWindowGroup {\n    ContentView()\n}\n.windowResizeAnchor(.topLeading)  \u002F\u002F Resize originates from top-left\n.windowResizeAnchor(.center)      \u002F\u002F Resize from center\n```\n\n### Menu Bar Commands (iPad)\n\n```swift\n@main\nstruct MyApp: App {\n    var body: some Scene {\n        WindowGroup {\n            ContentView()\n        }\n        .commands {\n            CommandMenu(\"View\") {\n                Button(\"Show Sidebar\") {\n                    showSidebar.toggle()\n                }\n                .keyboardShortcut(\"s\", modifiers: [.command, .option])\n\n                Divider()\n\n                Button(\"Zoom In\") { zoom += 0.1 }\n                    .keyboardShortcut(\"+\")\n                Button(\"Zoom Out\") { zoom -= 0.1 }\n                    .keyboardShortcut(\"-\")\n            }\n        }\n    }\n}\n```\n\n### NavigationSplitView Column Control\n\n```swift\n\u002F\u002F iOS 26: Automatic column visibility\nNavigationSplitView {\n    Sidebar()\n} content: {\n    ContentList()\n} detail: {\n    DetailView()\n}\n\u002F\u002F Columns auto-hide\u002Fshow based on available width\n\n\u002F\u002F Manual control (when needed)\n@State private var columnVisibility: NavigationSplitViewVisibility = .all\n\nNavigationSplitView(columnVisibility: $columnVisibility) {\n    Sidebar()\n} detail: {\n    DetailView()\n}\n```\n\n### Scene Phase\n\n```swift\n@Environment(\\.scenePhase) var scenePhase\n\nvar body: some View {\n    content\n        .onChange(of: scenePhase) { oldPhase, newPhase in\n            switch newPhase {\n            case .active:\n                \u002F\u002F Window is visible and interactive\n            case .inactive:\n                \u002F\u002F Window is visible but not interactive\n            case .background:\n                \u002F\u002F Window is not visible\n            }\n        }\n}\n```\n\n---\n\n## Coordinate Spaces\n\n### Built-in Coordinate Spaces\n\n```swift\n\u002F\u002F Global (screen coordinates)\nproxy.frame(in: .global)\n\n\u002F\u002F Local (view's own bounds)\nproxy.frame(in: .local)\n\n\u002F\u002F Named (custom)\nproxy.frame(in: .named(\"mySpace\"))\n```\n\n### Creating Named Spaces\n\n```swift\nScrollView {\n    content\n        .onGeometryChange(for: CGFloat.self) { proxy in\n            proxy.frame(in: .named(\"scroll\")).minY\n        } action: { offset in\n            scrollOffset = offset\n        }\n}\n.coordinateSpace(name: \"scroll\")\n\n\u002F\u002F iOS 17+ typed coordinate space\nextension CoordinateSpaceProtocol where Self == NamedCoordinateSpace {\n    static var scroll: Self { .named(\"scroll\") }\n}\n```\n\n---\n\n## ScrollView Geometry (iOS 18+)\n\n### onScrollGeometryChange\n\n```swift\nScrollView {\n    content\n}\n.onScrollGeometryChange(for: CGFloat.self) { geometry in\n    geometry.contentOffset.y\n} action: { offset in\n    scrollOffset = offset\n}\n```\n\n### ScrollGeometry Properties\n\n```swift\n.onScrollGeometryChange(for: ScrollGeometry.self) { $0 } action: { geo in\n    let offset = geo.contentOffset      \u002F\u002F Current scroll position\n    let size = geo.contentSize          \u002F\u002F Total content size\n    let visible = geo.visibleRect       \u002F\u002F Currently visible rect\n    let insets = geo.contentInsets      \u002F\u002F Content insets\n}\n```\n\n---\n\n## Lazy Container Gotchas\n\n### Recycling Behavior\n\n`LazyVStack` and `LazyHStack` create views **on demand** and recycle them when off-screen. This means:\n\n- **View identity matters**: If cells flash\u002Fdisappear during fast scrolling, the view identity is unstable. Use explicit `.id()` on items.\n- **onAppear\u002FonDisappear fire repeatedly**: Views are created and destroyed as you scroll. Don't use these for one-time setup.\n- **State resets on recycle**: `@State` in lazy items resets when recycled. Lift state to the model layer.\n\n```swift\n\u002F\u002F ❌ Items flash during fast scroll — unstable identity\nLazyVStack {\n    ForEach(Array(items.enumerated()), id: \\.offset) { index, item in\n        ItemRow(item: item)  \u002F\u002F Identity changes when array mutates\n    }\n}\n\n\u002F\u002F ✅ Stable identity prevents flash\u002Fdisappear\nLazyVStack {\n    ForEach(items) { item in  \u002F\u002F Uses item.id (Identifiable)\n        ItemRow(item: item)\n    }\n}\n```\n\n### When NOT to Use Lazy Containers\n\n| Scenario | Use Instead | Why |\n|----------|-------------|-----|\n| \u003C 50 items | `VStack` \u002F `HStack` | No recycling overhead, simpler |\n| Nested in another lazy container | `VStack` (inner) | Nested lazy causes layout issues |\n| Need all items measured upfront | `VStack` | Lazy containers don't know total size |\n\n---\n\n## Resources\n\n**WWDC**: 2025-208, 2024-10074, 2022-10056\n\n**Docs**: \u002Fswiftui\u002Flayout, \u002Fswiftui\u002Fviewthatfits\n\n**Skills**: axiom-swiftui-layout, axiom-swiftui-debugging\n\n","# SwiftUI Layout API Reference\n\nComprehensive API reference for SwiftUI adaptive layout tools. For decision guidance and anti-patterns, see the `axiom-swiftui-layout` skill.\n\n## Overview\n\nThis reference covers all SwiftUI layout APIs for building adaptive interfaces:\n\n- **ViewThatFits** — Automatic variant selection (iOS 16+)\n- **AnyLayout** — Type-erased animated layout switching (iOS 16+)\n- **Layout Protocol** — Custom layout algorithms (iOS 16+)\n- **onGeometryChange** — Efficient geometry reading (iOS 16+ backported)\n- **GeometryReader** — Layout-phase geometry access (iOS 13+)\n- **Safe Area Padding** — .safeAreaPadding() vs .padding() (iOS 17+)\n- **Size Classes** — Trait-based adaptation\n- **iOS 26 Window APIs** — Free-form windows, menu bar, resize anchors\n\n---\n\n## ViewThatFits\n\nEvaluates child views in order and displays the first one that fits in the available space.\n\n### Basic Usage\n\n```swift\nViewThatFits {\n    \u002F\u002F First choice\n    HStack {\n        icon\n        title\n        Spacer()\n        button\n    }\n\n    \u002F\u002F Second choice\n    HStack {\n        icon\n        title\n        button\n    }\n\n    \u002F\u002F Fallback\n    VStack {\n        HStack { icon; title }\n        button\n    }\n}\n```\n\n### With Axis Constraint\n\n```swift\n\u002F\u002F Only consider horizontal fit\nViewThatFits(in: .horizontal) {\n    wideVersion\n    narrowVersion\n}\n\n\u002F\u002F Only consider vertical fit\nViewThatFits(in: .vertical) {\n    tallVersion\n    shortVersion\n}\n```\n\n### How It Works\n\n1. Applies `fixedSize()` to each child\n2. Measures ideal size against available space\n3. Returns first child that fits\n4. Falls back to last child if none fit\n\n### Limitations\n\n- Does not expose which variant was selected\n- Cannot animate between variants (use AnyLayout instead)\n- Measures all variants (performance consideration for complex views)\n\n---\n\n## AnyLayout\n\nType-erased layout container enabling animated transitions between layouts.\n\n### Basic Usage\n\n```swift\nstruct AdaptiveView: View {\n    @Environment(\\.horizontalSizeClass) var sizeClass\n\n    var layout: AnyLayout {\n        sizeClass == .compact\n            ? AnyLayout(VStackLayout(spacing: 12))\n            : AnyLayout(HStackLayout(spacing: 20))\n    }\n\n    var body: some View {\n        layout {\n            ForEach(items) { item in\n                ItemView(item: item)\n            }\n        }\n        .animation(.default, value: sizeClass)\n    }\n}\n```\n\n### Available Layout Types\n\n```swift\nAnyLayout(HStackLayout(alignment: .top, spacing: 10))\nAnyLayout(VStackLayout(alignment: .leading, spacing: 8))\nAnyLayout(ZStackLayout(alignment: .center))\nAnyLayout(GridLayout(alignment: .leading, horizontalSpacing: 10, verticalSpacing: 10))\n```\n\n### Custom Conditions\n\n```swift\n\u002F\u002F Based on Dynamic Type\n@Environment(\\.dynamicTypeSize) var typeSize\n\nvar layout: AnyLayout {\n    typeSize.isAccessibilitySize\n        ? AnyLayout(VStackLayout())\n        : AnyLayout(HStackLayout())\n}\n\n\u002F\u002F Based on geometry\n@State private var isWide = true\n\nvar layout: AnyLayout {\n    isWide\n        ? AnyLayout(HStackLayout())\n        : AnyLayout(VStackLayout())\n}\n```\n\n### Why Use Over Conditional Views\n\n```swift\n\u002F\u002F ❌ Loses view identity, no animation\nif isCompact {\n    VStack { content }\n} else {\n    HStack { content }\n}\n\n\u002F\u002F ✅ Preserves identity, smooth animation\nlet layout = isCompact ? AnyLayout(VStackLayout()) : AnyLayout(HStackLayout())\nlayout { content }\n```\n\n---\n\n## Layout Protocol\n\nCreate custom layout containers with full control over positioning.\n\n### Basic Custom Layout\n\n```swift\nstruct FlowLayout: Layout {\n    var spacing: CGFloat = 8\n\n    func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {\n        let sizes = subviews.map { $0.sizeThatFits(.unspecified) }\n        return calculateSize(for: sizes, in: proposal.width ?? .infinity)\n    }\n\n    func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {\n        var point = bounds.origin\n        var lineHeight: CGFloat = 0\n\n        for subview in subviews {\n            let size = subview.sizeThatFits(.unspecified)\n\n            if point.x + size.width > bounds.maxX {\n                point.x = bounds.origin.x\n                point.y += lineHeight + spacing\n                lineHeight = 0\n            }\n\n            subview.place(at: point, proposal: .unspecified)\n            point.x += size.width + spacing\n            lineHeight = max(lineHeight, size.height)\n        }\n    }\n}\n\n\u002F\u002F Usage\nFlowLayout(spacing: 12) {\n    ForEach(tags) { tag in\n        TagView(tag: tag)\n    }\n}\n```\n\n### With Cache\n\n```swift\nstruct CachedLayout: Layout {\n    struct CacheData {\n        var sizes: [CGSize] = []\n    }\n\n    func makeCache(subviews: Subviews) -> CacheData {\n        CacheData(sizes: subviews.map { $0.sizeThatFits(.unspecified) })\n    }\n\n    func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout CacheData) -> CGSize {\n        \u002F\u002F Use cache.sizes instead of measuring again\n    }\n}\n```\n\n### Layout Values\n\n```swift\n\u002F\u002F Define custom layout value\nstruct Rank: LayoutValueKey {\n    static let defaultValue: Int = 0\n}\n\nextension View {\n    func rank(_ value: Int) -> some View {\n        layoutValue(key: Rank.self, value: value)\n    }\n}\n\n\u002F\u002F Read in layout\nfunc placeSubviews(...) {\n    let sorted = subviews.sorted { $0[Rank.self] \u003C $1[Rank.self] }\n}\n```\n\n---\n\n## onGeometryChange\n\nEfficient geometry reading without layout side effects. Backported to iOS 16+.\n\n### Basic Usage\n\n```swift\n@State private var size: CGSize = .zero\n\nvar body: some View {\n    content\n        .onGeometryChange(for: CGSize.self) { proxy in\n            proxy.size\n        } action: { newSize in\n            size = newSize\n        }\n}\n```\n\n### Reading Specific Values\n\n```swift\n\u002F\u002F Width only\n.onGeometryChange(for: CGFloat.self) { proxy in\n    proxy.size.width\n} action: { width in\n    columnCount = max(1, Int(width \u002F 150))\n}\n\n\u002F\u002F Frame in coordinate space\n.onGeometryChange(for: CGRect.self) { proxy in\n    proxy.frame(in: .global)\n} action: { frame in\n    globalFrame = frame\n}\n\n\u002F\u002F Aspect ratio\n.onGeometryChange(for: Bool.self) { proxy in\n    proxy.size.width > proxy.size.height\n} action: { isWide in\n    self.isWide = isWide\n}\n```\n\n### Coordinate Spaces\n\n```swift\n\u002F\u002F Named coordinate space\nScrollView {\n    content\n        .onGeometryChange(for: CGFloat.self) { proxy in\n            proxy.frame(in: .named(\"scroll\")).minY\n        } action: { offset in\n            scrollOffset = offset\n        }\n}\n.coordinateSpace(name: \"scroll\")\n```\n\n### Comparison with GeometryReader\n\n| Aspect | onGeometryChange | GeometryReader |\n|--------|------------------|----------------|\n| Layout impact | None | Greedy (fills space) |\n| When evaluated | After layout | During layout |\n| Use case | Side effects | Layout calculations |\n| iOS version | 16+ (backported) | 13+ |\n\n---\n\n## GeometryReader\n\nProvides geometry information during layout phase. Use sparingly due to greedy sizing.\n\n### Basic Usage (Constrained)\n\n```swift\n\u002F\u002F ✅ Always constrain GeometryReader\nGeometryReader { proxy in\n    let width = proxy.size.width\n    HStack(spacing: 0) {\n        Rectangle().frame(width: width * 0.3)\n        Rectangle().frame(width: width * 0.7)\n    }\n}\n.frame(height: 100)  \u002F\u002F Required constraint\n```\n\n### GeometryProxy Properties\n\n```swift\nGeometryReader { proxy in\n    \u002F\u002F Container size\n    let size = proxy.size  \u002F\u002F CGSize\n\n    \u002F\u002F Safe area insets\n    let insets = proxy.safeAreaInsets  \u002F\u002F EdgeInsets\n\n    \u002F\u002F Frame in coordinate space\n    let globalFrame = proxy.frame(in: .global)\n    let localFrame = proxy.frame(in: .local)\n    let namedFrame = proxy.frame(in: .named(\"container\"))\n}\n```\n\n### Common Patterns\n\n```swift\n\u002F\u002F Proportional sizing\nGeometryReader { geo in\n    VStack {\n        header.frame(height: geo.size.height * 0.2)\n        content.frame(height: geo.size.height * 0.8)\n    }\n}\n\n\u002F\u002F Centering with offset\nGeometryReader { geo in\n    content\n        .position(x: geo.size.width \u002F 2, y: geo.size.height \u002F 2)\n}\n```\n\n### Avoiding Common Mistakes\n\n```swift\n\u002F\u002F ❌ Unconstrained in VStack\nVStack {\n    GeometryReader { ... }  \u002F\u002F Takes ALL space\n    Button(\"Next\") { }       \u002F\u002F Invisible\n}\n\n\u002F\u002F ✅ Constrained\nVStack {\n    GeometryReader { ... }\n        .frame(height: 200)\n    Button(\"Next\") { }\n}\n\n\u002F\u002F ❌ Causing layout loops\nGeometryReader { geo in\n    content\n        .frame(width: geo.size.width)  \u002F\u002F Can cause infinite loop\n}\n```\n\n---\n\n## Safe Area Padding\n\nSwiftUI provides two primary approaches for handling spacing around content: `.padding()` and `.safeAreaPadding()`. Understanding when to use each is critical for proper layout on devices with safe areas (notch, Dynamic Island, home indicator).\n\n### The Critical Difference\n\n```swift\n\u002F\u002F ❌ WRONG - Ignores safe areas, content hits notch\u002Fhome indicator\nScrollView {\n    content\n}\n.padding(.horizontal, 20)\n\n\u002F\u002F ✅ CORRECT - Respects safe areas, adds padding beyond them\nScrollView {\n    content\n}\n.safeAreaPadding(.horizontal, 20)\n```\n\n**Key insight**: `.padding()` adds fixed spacing from the view's edges. `.safeAreaPadding()` adds spacing beyond the safe area insets.\n\n### When to Use Each\n\n#### Use `.padding()` when\n\n- Adding spacing between sibling views within a container\n- Creating internal spacing that should be consistent everywhere\n- Working with views that already respect safe areas (like List, Form)\n- Adding decorative spacing on macOS (no safe area concerns)\n\n```swift\nVStack(spacing: 0) {\n    header\n        .padding(.horizontal, 16)  \u002F\u002F ✅ Internal spacing\n\n    Divider()\n\n    content\n        .padding(.horizontal, 16)  \u002F\u002F ✅ Internal spacing\n}\n```\n\n#### Use `.safeAreaPadding()` when (iOS 17+)\n\n- Adding margin to full-width content that extends to screen edges\n- Implementing edge-to-edge scrolling with proper insets\n- Creating custom containers that need safe area awareness\n- Working with Liquid Glass or full-screen materials\n\n```swift\n\u002F\u002F ✅ Edge-to-edge list with custom padding\nList(items) { item in\n    ItemRow(item)\n}\n.listStyle(.plain)\n.safeAreaPadding(.horizontal, 20)  \u002F\u002F Adds 20pt beyond safe areas\n\n\u002F\u002F ✅ Full-screen content with proper margins\nZStack {\n    Color.blue.ignoresSafeArea()\n\n    VStack {\n        content\n    }\n    .safeAreaPadding(.all, 16)  \u002F\u002F Respects notch, home indicator\n}\n```\n\n### Platform Availability\n\n**iOS 17+, iPadOS 17+, macOS 14+, axiom-visionOS 1.0+**\n\nFor earlier iOS versions, use manual safe area handling:\n\n```swift\n\u002F\u002F iOS 13-16 fallback\nGeometryReader { geo in\n    content\n        .padding(.horizontal, 20 + geo.safeAreaInsets.leading)\n}\n```\n\nOr conditional compilation:\n\n```swift\nif #available(iOS 17, *) {\n    content.safeAreaPadding(.horizontal, 20)\n} else {\n    content.padding(.horizontal, 20)\n        .padding(.leading, safeAreaInsets.leading)\n}\n```\n\n### Edge-Specific Usage\n\n```swift\n\u002F\u002F Top only (below status bar\u002Fnotch)\n.safeAreaPadding(.top, 8)\n\n\u002F\u002F Bottom only (above home indicator)\n.safeAreaPadding(.bottom, 16)\n\n\u002F\u002F Horizontal (left\u002Fright of safe areas)\n.safeAreaPadding(.horizontal, 20)\n\n\u002F\u002F All edges\n.safeAreaPadding(.all, 16)\n\n\u002F\u002F Individual edges\n.safeAreaPadding(EdgeInsets(top: 8, leading: 20, bottom: 16, trailing: 20))\n```\n\n### Common Patterns\n\n#### Edge-to-Edge ScrollView\n\n```swift\nScrollView {\n    LazyVStack(spacing: 12) {\n        ForEach(items) { item in\n            ItemCard(item)\n        }\n    }\n}\n.safeAreaPadding(.horizontal, 16)  \u002F\u002F Content inset from edges + safe areas\n.safeAreaPadding(.vertical, 8)\n```\n\n#### Full-Screen Background with Safe Content\n\n```swift\nZStack {\n    \u002F\u002F Background extends edge-to-edge\n    LinearGradient(...)\n        .ignoresSafeArea()\n\n    \u002F\u002F Content respects safe areas + custom padding\n    VStack {\n        header\n        Spacer()\n        content\n        Spacer()\n        footer\n    }\n    .safeAreaPadding(.all, 20)\n}\n```\n\n#### Nested Padding (Combined Approach)\n\n```swift\n\u002F\u002F Outer: Safe area padding for device insets\nVStack(spacing: 0) {\n    content\n}\n.safeAreaPadding(.horizontal, 16)  \u002F\u002F Beyond safe areas\n\n\u002F\u002F Inner: Regular padding for internal spacing\nVStack {\n    Text(\"Title\")\n        .padding(.bottom, 8)  \u002F\u002F Internal spacing\n    Text(\"Subtitle\")\n}\n```\n\n### Decision Tree\n\n```\nDoes your content extend to screen edges?\n├─ YES → Use .safeAreaPadding()\n│   ├─ Is it scrollable? → .safeAreaPadding(.horizontal\u002F.vertical)\n│   └─ Is it full-screen? → .safeAreaPadding(.all)\n│\n└─ NO (contained within a safe container like List\u002FForm)\n    └─ Use .padding() for internal spacing\n```\n\n### Visual Debugging\n\n```swift\n\u002F\u002F Visualize safe area padding (iOS 17+)\ncontent\n    .safeAreaPadding(.horizontal, 20)\n    .background(.red.opacity(0.2))  \u002F\u002F Shows padding area\n    .border(.blue)  \u002F\u002F Shows content bounds\n```\n\n### Migration from Manual Safe Area Handling\n\n```swift\n\u002F\u002F ❌ OLD: Manual calculation (iOS 13-16)\nGeometryReader { geo in\n    content\n        .padding(.top, geo.safeAreaInsets.top + 16)\n        .padding(.bottom, geo.safeAreaInsets.bottom + 16)\n        .padding(.horizontal, 20)\n}\n\n\u002F\u002F ✅ NEW: .safeAreaPadding() (iOS 17+)\ncontent\n    .safeAreaPadding(.vertical, 16)\n    .safeAreaPadding(.horizontal, 20)\n```\n\n### Related APIs\n\n**`.safeAreaInset(edge:)`** - Adds persistent content that shrinks the safe area:\n```swift\nScrollView {\n    content\n}\n.safeAreaInset(edge: .bottom) {\n    \u002F\u002F This REDUCES the safe area, content scrolls under it\n    toolbarButtons\n        .padding()\n        .background(.ultraThinMaterial)\n}\n```\n\n**`.ignoresSafeArea()`** - Opts out of safe area completely:\n```swift\nColor.blue\n    .ignoresSafeArea()  \u002F\u002F Extends to absolute screen edges\n```\n\n### Why It Matters\n\n**Before iOS 17**: Developers had to manually calculate safe area insets with GeometryReader, leading to:\n- Verbose code\n- Performance overhead (GeometryReader forces extra layout pass)\n- Easy mistakes (forgetting to check all edges)\n\n**iOS 17+**: `.safeAreaPadding()` provides:\n- Declarative API (matches SwiftUI philosophy)\n- Automatic safe area awareness\n- Better performance (no extra layout passes)\n- Type-safe edge specification\n\n**Real-world impact**: Using `.padding()` instead of `.safeAreaPadding()` on iPhone 15 Pro causes content to:\n- Hit the Dynamic Island (top)\n- Overlap the home indicator (bottom)\n- Get cut off by screen corners (rounded edges)\n\n---\n\n## Size Classes\n\nEnvironment values indicating horizontal and vertical size characteristics.\n\n### Reading Size Classes\n\n```swift\nstruct AdaptiveView: View {\n    @Environment(\\.horizontalSizeClass) var horizontalSizeClass\n    @Environment(\\.verticalSizeClass) var verticalSizeClass\n\n    var body: some View {\n        if horizontalSizeClass == .compact {\n            compactLayout\n        } else {\n            regularLayout\n        }\n    }\n}\n```\n\n### Size Class Values\n\n```swift\nenum UserInterfaceSizeClass {\n    case compact    \u002F\u002F Constrained space\n    case regular    \u002F\u002F Ample space\n}\n```\n\n### Platform Behavior\n\n**iPhone:**\n| Orientation | Horizontal | Vertical |\n|-------------|------------|----------|\n| Portrait | `.compact` | `.regular` |\n| Landscape (small) | `.compact` | `.compact` |\n| Landscape (Plus\u002FMax) | `.regular` | `.compact` |\n\n**iPad:**\n| Configuration | Horizontal | Vertical |\n|--------------|------------|----------|\n| Any full screen | `.regular` | `.regular` |\n| 70% Split View | `.regular` | `.regular` |\n| 50% Split View | `.regular` | `.regular` |\n| 33% Split View | `.compact` | `.regular` |\n| Slide Over | `.compact` | `.regular` |\n\n### Overriding Size Classes\n\n```swift\ncontent\n    .environment(\\.horizontalSizeClass, .compact)\n```\n\n---\n\n## Dynamic Type Size\n\nEnvironment value for user's preferred text size.\n\n### Reading Dynamic Type\n\n```swift\n@Environment(\\.dynamicTypeSize) var dynamicTypeSize\n\nvar body: some View {\n    if dynamicTypeSize.isAccessibilitySize {\n        accessibleLayout\n    } else {\n        standardLayout\n    }\n}\n```\n\n### Size Categories\n\n```swift\nenum DynamicTypeSize: Comparable {\n    case xSmall\n    case small\n    case medium\n    case large           \u002F\u002F Default\n    case xLarge\n    case xxLarge\n    case xxxLarge\n    case accessibility1  \u002F\u002F isAccessibilitySize = true\n    case accessibility2\n    case accessibility3\n    case accessibility4\n    case accessibility5\n}\n```\n\n### Scaled Metric\n\n```swift\n@ScaledMetric var iconSize: CGFloat = 24\n@ScaledMetric(relativeTo: .largeTitle) var headerSize: CGFloat = 44\n\nImage(systemName: \"star\")\n    .frame(width: iconSize, height: iconSize)\n```\n\n---\n\n## iOS 26 Window APIs\n\n### Window Resize Anchor\n\n```swift\nWindowGroup {\n    ContentView()\n}\n.windowResizeAnchor(.topLeading)  \u002F\u002F Resize originates from top-left\n.windowResizeAnchor(.center)      \u002F\u002F Resize from center\n```\n\n### Menu Bar Commands (iPad)\n\n```swift\n@main\nstruct MyApp: App {\n    var body: some Scene {\n        WindowGroup {\n            ContentView()\n        }\n        .commands {\n            CommandMenu(\"View\") {\n                Button(\"Show Sidebar\") {\n                    showSidebar.toggle()\n                }\n                .keyboardShortcut(\"s\", modifiers: [.command, .option])\n\n                Divider()\n\n                Button(\"Zoom In\") { zoom += 0.1 }\n                    .keyboardShortcut(\"+\")\n                Button(\"Zoom Out\") { zoom -= 0.1 }\n                    .keyboardShortcut(\"-\")\n            }\n        }\n    }\n}\n```\n\n### NavigationSplitView Column Control\n\n```swift\n\u002F\u002F iOS 26: Automatic column visibility\nNavigationSplitView {\n    Sidebar()\n} content: {\n    ContentList()\n} detail: {\n    DetailView()\n}\n\u002F\u002F Columns auto-hide\u002Fshow based on available width\n\n\u002F\u002F Manual control (when needed)\n@State private var columnVisibility: NavigationSplitViewVisibility = .all\n\nNavigationSplitView(columnVisibility: $columnVisibility) {\n    Sidebar()\n} detail: {\n    DetailView()\n}\n```\n\n### Scene Phase\n\n```swift\n@Environment(\\.scenePhase) var scenePhase\n\nvar body: some View {\n    content\n        .onChange(of: scenePhase) { oldPhase, newPhase in\n            switch newPhase {\n            case .active:\n                \u002F\u002F Window is visible and interactive\n            case .inactive:\n                \u002F\u002F Window is visible but not interactive\n            case .background:\n                \u002F\u002F Window is not visible\n            }\n        }\n}\n```\n\n---\n\n## Coordinate Spaces\n\n### Built-in Coordinate Spaces\n\n```swift\n\u002F\u002F Global (screen coordinates)\nproxy.frame(in: .global)\n\n\u002F\u002F Local (view's own bounds)\nproxy.frame(in: .local)\n\n\u002F\u002F Named (custom)\nproxy.frame(in: .named(\"mySpace\"))\n```\n\n### Creating Named Spaces\n\n```swift\nScrollView {\n    content\n        .onGeometryChange(for: CGFloat.self) { proxy in\n            proxy.frame(in: .named(\"scroll\")).minY\n        } action: { offset in\n            scrollOffset = offset\n        }\n}\n.coordinateSpace(name: \"scroll\")\n\n\u002F\u002F iOS 17+ typed coordinate space\nextension CoordinateSpaceProtocol where Self == NamedCoordinateSpace {\n    static var scroll: Self { .named(\"scroll\") }\n}\n```\n\n---\n\n## ScrollView Geometry (iOS 18+)\n\n### onScrollGeometryChange\n\n```swift\nScrollView {\n    content\n}\n.onScrollGeometryChange(for: CGFloat.self) { geometry in\n    geometry.contentOffset.y\n} action: { offset in\n    scrollOffset = offset\n}\n```\n\n### ScrollGeometry Properties\n\n```swift\n.onScrollGeometryChange(for: ScrollGeometry.self) { $0 } action: { geo in\n    let offset = geo.contentOffset      \u002F\u002F Current scroll position\n    let size = geo.contentSize          \u002F\u002F Total content size\n    let visible = geo.visibleRect       \u002F\u002F Currently visible rect\n    let insets = geo.contentInsets      \u002F\u002F Content insets\n}\n```\n\n---\n\n## Lazy Container Gotchas\n\n### Recycling Behavior\n\n`LazyVStack` and `LazyHStack` create views **on demand** and recycle them when off-screen. This means:\n\n- **View identity matters**: If cells flash\u002Fdisappear during fast scrolling, the view identity is unstable. Use explicit `.id()` on items.\n- **onAppear\u002FonDisappear fire repeatedly**: Views are created and destroyed as you scroll. Don't use these for one-time setup.\n- **State resets on recycle**: `@State` in lazy items resets when recycled. Lift state to the model layer.\n\n```swift\n\u002F\u002F ❌ Items flash during fast scroll — unstable identity\nLazyVStack {\n    ForEach(Array(items.enumerated()), id: \\.offset) { index, item in\n        ItemRow(item: item)  \u002F\u002F Identity changes when array mutates\n    }\n}\n\n\u002F\u002F ✅ Stable identity prevents flash\u002Fdisappear\nLazyVStack {\n    ForEach(items) { item in  \u002F\u002F Uses item.id (Identifiable)\n        ItemRow(item: item)\n    }\n}\n```\n\n### When NOT to Use Lazy Containers\n\n| Scenario | Use Instead | Why |\n|----------|-------------|-----|\n| \u003C 50 items | `VStack` \u002F `HStack` | No recycling overhead, simpler |\n| Nested in another lazy container | `VStack` (inner) | Nested lazy causes layout issues |\n| Need all items measured upfront | `VStack` | Lazy containers don't know total size |\n\n---\n\n## Resources\n\n**WWDC**: 2025-208, 2024-10074, 2022-10056\n\n**Docs**: \u002Fswiftui\u002Flayout, \u002Fswiftui\u002Fviewthatfits\n\n**Skills**: axiom-swiftui-layout, axiom-swiftui-debugging\n\n",{"name":5,"license":15,"metadata":38,"description":7},{"version":39},"1.0.0","main","def8531e65114c0fca8fb8551c1871ee0eed705c","skills\u002FCharlesWiltgen\u002FAxiom\u002Faxiom-swiftui-layout-ref",24,"https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub","https:\u002F\u002Fskillshub.wtf","https:\u002F\u002Fgithub.com\u002FComeOnOliver","AI & Blockchain Engineer\r\n","2026-06-24 22:07:19",{"source":8,"skillName":5,"commands":50,"prompt":60,"zipAvailable":61},[51,54,57],{"tool":52,"command":53},"npx","npx skills add ComeOnOliver\u002Fskillshub --skill axiom-swiftui-layout-ref",{"tool":55,"command":56},"bunx","bunx skills add ComeOnOliver\u002Fskillshub --skill axiom-swiftui-layout-ref",{"tool":58,"command":59},"pnpm","pnpm dlx skills add ComeOnOliver\u002Fskillshub --skill axiom-swiftui-layout-ref","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002FCharlesWiltgen\u002FAxiom\u002Faxiom-swiftui-layout-ref\u002FSKILL.md",true,[],[64,78,88,97,103,110],{"skillNo":65,"name":66,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":67,"categories":68,"keywords":74,"publishedAt":33},"01M6FKJN13GMG4KY57J3QD8GY5","model-hierarchy-skill","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzscole\u002Fmodel-hierarchy-skill\u002Fmodel-hierarchy-skill\u002FSKILL.md",[69],{"categoryNo":70,"categoryName":71,"taxonomyType":72,"taxonomyTypeDesc":73},"01JBSEEDCAT000000000000023","Skill与智能体开发","SCENARIO","使用场景",[75,76,77],"model","model hierarchy","model hierarchy skill",{"skillNo":79,"name":80,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":81,"categories":82,"keywords":84,"publishedAt":33},"01M6FKJJJ6GCAMR4PPPRD12M5X","zoom-mcp","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-mcp\u002FSKILL.md",[83],{"categoryNo":70,"categoryName":71,"taxonomyType":72,"taxonomyTypeDesc":73},[85,86,87],"MCP","zoom","zoom mcp",{"skillNo":89,"name":90,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":91,"categories":92,"keywords":93,"publishedAt":33},"01M6FKJE3KYT3KPEZMEEHYKA6K","zoom-apps-sdk","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fzoom-apps-sdk\u002FSKILL.md",[],[94,95,96,86],"sdk","zoom apps sdk","apps",{"skillNo":98,"name":99,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":100,"categories":101,"keywords":102,"publishedAt":33},"01M6FKJ9EDH9Q5VWDF8HAJRSP0","windows","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwindows\u002FSKILL.md",[],[99],{"skillNo":104,"name":105,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":106,"categories":107,"keywords":109,"publishedAt":33},"01M6FKJ666BEEFC3AE9GJ72Q93","whiteboard","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwhiteboard\u002FSKILL.md",[108],{"categoryNo":70,"categoryName":71,"taxonomyType":72,"taxonomyTypeDesc":73},[105],{"skillNo":111,"name":112,"nameZh":6,"description":6,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":113,"categories":114,"keywords":115,"publishedAt":33},"01M6FKJ21WFCDGTJQ2Q91T9DVW","websockets","https:\u002F\u002Fgithub.com\u002FComeOnOliver\u002Fskillshub\u002Fblob\u002Fmain\u002Fskills\u002Fzoom\u002Fskills\u002Fwebsockets\u002FSKILL.md",[],[112]]