Token导航 LogoToken导航

Agent Skill · 前端设计

axiom-swiftui-layout-ref(axiom swiftui 布局参考)

用于辅助界面设计、视觉规范、排版、配色、布局和交互体验优化。它适合让 Agent 根据产品场景整理页面结构、生成 UI 方案、检查视觉一致性或改进组件层级。使用时需要结合现有品牌、设计系统和用户任务,不应只堆装饰元素;涉及真实页面改动时,应通过截图或浏览器预览检查文本溢出、对齐和响应式表现。

axiom-swiftui-layout-ref 是前端设计类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为需要联网;许可证为 MIT,第三方扫描已通过,按安装量在前端设计类的 10,216 个 Skill 里排第 1,659。

总安装 4,422周安装 188GitHub Stars 873下载 1,549核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
MIT

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:axiom-swiftui-layout-ref(axiom swiftui 布局参考)
来源仓库:https://github.com/charleswiltgen/axiom
仓库路径:skills/axiom-swiftui-layout-ref
安装命令:
npx skills add https://github.com/charleswiltgen/axiom --skill axiom-swiftui-layout-ref
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。不同来源提供的安装方式可能略有差异;本站展示可直接复制的安装命令,安装前请核对来源页面。

skills.shnpx skills
npx skills add https://github.com/charleswiltgen/axiom --skill axiom-swiftui-layout-ref

这个 Skill 是做什么的

前端设计类 Skill 关注页面结构、视觉规范和组件写法,输出通常是可运行的界面代码。

axiom-swiftui-layout-ref 是 SwiftUI 布局 API 的完整参考手册,涵盖 ViewThatFits、AnyLayout 等技术细节。适用于查阅特定布局原语的参数定义、动画切换机制及 iOS 版本兼容性说明。

可作为决策依据的补充资料,但布局策略建议优先参考 axiom-swiftui-layout 技能。内容基于 iOS 16+ 特性编写,需注意目标项目的最低系统要求匹配情况。未包含布局陷阱分析,相关指导请参见同系列其他诊断类技能文档。

来源仓库内路径:skills/axiom-swiftui-layout-ref

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/charleswiltgen/axiom --skill axiom-swiftui-lay…;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它需要需要联网,先在测试目录或测试账号里跑,确认没问题再放到正式项目。

常见问题

6 条

axiom-swiftui-layout-ref 怎么安装?

在终端执行 npx skills add https://github.com/charleswiltgen/axiom --skill axiom-swiftui-layout-ref。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

axiom-swiftui-layout-ref 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

axiom-swiftui-layout-ref 需要什么权限?

权限级别是需要联网。该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

axiom-swiftui-layout-ref 是免费的吗?

来源标注的许可证是 MIT。本站不代收任何费用,也不托管安装包;能否商用、要不要署名,以许可证原文为准。

axiom-swiftui-layout-ref 用的人多吗?

公开数据是累计安装 4,422 次,近一周安装 188 次,下载 1,549 次,GitHub 873 星。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

axiom-swiftui-layout-ref 安全吗?

第三方扫描结果为通过,但这只覆盖已知的静态检查项,不能替代自己读一遍源码和安装命令。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。
  • 3 个目录同时收录,说明这个 Skill 流传比较广。
  • 许可证明确为 MIT,商用和分发边界好判断。
  • 第三方扫描已通过,常见的高风险行为没有命中。

先想清楚的地方

  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理前端设计类任务、又不想每次都手写提示词的人。
  • 需要许可证清楚、并且有第三方扫描结果的团队项目。

不太适合

  • 需要厂商级技术支持和 SLA 的生产系统。

前端设计类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
frontend-design

前端设计 · Codex、Claude

8,823,360126,072只读MIT通过
vercel-composition-patterns

Vercel 组合模式 · Codex、Claude

3,858,81625,893需要联网MIT通过
vercel-react-native-skills

Vercel React native skills 前端 · Codex、Claude

2,468,66425,878只读MIT通过
adapt

adapt 搜索 · Codex、Claude

2,027,04023,125操作浏览器未明确通过
bolder

bolder 分析 · Codex、Claude

2,004,28823,130只读未明确通过
axiom-swiftui-layout-ref

axiom swiftui 布局参考 · 本页

4,422873需要联网MIT通过

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

SKILL.md 原文

SwiftUI Layout API Reference

Comprehensive API reference for SwiftUI adaptive layout tools. For decision guidance and anti-patterns, see the axiom-swiftui-layout skill.

Overview

This reference covers all SwiftUI layout APIs for building adaptive interfaces:

  • ViewThatFits — Automatic variant selection (iOS 16+)
  • AnyLayout — Type-erased animated layout switching (iOS 16+)
  • Layout Protocol — Custom layout algorithms (iOS 16+)
  • onGeometryChange — Efficient geometry reading (iOS 16+ backported)
  • GeometryReader — Layout-phase geometry access (iOS 13+)
  • Safe Area Padding —.safeAreaPadding() vs.padding() (iOS 17+)
  • Size Classes — Trait-based adaptation
  • iOS 26 Window APIs — Free-form windows, menu bar, resize anchors

ViewThatFits

Evaluates child views in order and displays the first one that fits in the available space.

Basic Usage

ViewThatFits {
    // First choice
    HStack {
        icon
        title
        Spacer()
        button
    }

    // Second choice
    HStack {
        icon
        title
        button
    }

    // Fallback
    VStack {
        HStack { icon; title }
        button
    }
}

With Axis Constraint

// Only consider horizontal fit
ViewThatFits(in: .horizontal) {
    wideVersion
    narrowVersion
}

// Only consider vertical fit
ViewThatFits(in: .vertical) {
    tallVersion
    shortVersion
}

How It Works

  1. Applies fixedSize() to each child
  2. Measures ideal size against available space
  3. Returns first child that fits
  4. Falls back to last child if none fit

Limitations

  • Does not expose which variant was selected
  • Cannot animate between variants (use AnyLayout instead)
  • Measures all variants (performance consideration for complex views)

AnyLayout

Type-erased layout container enabling animated transitions between layouts.

Basic Usage

struct AdaptiveView: View {
    @Environment(\.horizontalSizeClass) var sizeClass

    var layout: AnyLayout {
        sizeClass == .compact
            ? AnyLayout(VStackLayout(spacing: 12))
            : AnyLayout(HStackLayout(spacing: 20))
    }

    var body: some View {
        layout {
            ForEach(items) { item in
                ItemView(item: item)
            }
        }
        .animation(.default, value: sizeClass)
    }
}

Available Layout Types

AnyLayout(HStackLayout(alignment: .top, spacing: 10))
AnyLayout(VStackLayout(alignment: .leading, spacing: 8))
AnyLayout(ZStackLayout(alignment: .center))
AnyLayout(GridLayout(alignment: .leading, horizontalSpacing: 10, verticalSpacing: 10))

Custom Conditions

// Based on Dynamic Type
@Environment(\.dynamicTypeSize) var typeSize

var layout: AnyLayout {
    typeSize.isAccessibilitySize
        ? AnyLayout(VStackLayout())
        : AnyLayout(HStackLayout())
}

// Based on geometry
@State private var isWide = true

var layout: AnyLayout {
    isWide
        ? AnyLayout(HStackLayout())
        : AnyLayout(VStackLayout())
}

Why Use Over Conditional Views

// ❌ Loses view identity, no animation
if isCompact {
    VStack { content }
} else {
    HStack { content }
}

// ✅ Preserves identity, smooth animation
let layout = isCompact ? AnyLayout(VStackLayout()) : AnyLayout(HStackLayout())
layout { content }

Layout Protocol

Create custom layout containers with full control over positioning.

Basic Custom Layout

struct FlowLayout: Layout {
    var spacing: CGFloat = 8

    func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) -> CGSize {
        let sizes = subviews.map { $0.sizeThatFits(.unspecified) }
        return calculateSize(for: sizes, in: proposal.width ?? .infinity)
    }

    func placeSubviews(in bounds: CGRect, proposal: ProposedViewSize, subviews: Subviews, cache: inout ()) {
        var point = bounds.origin
        var lineHeight: CGFloat = 0

        for subview in subviews {
            let size = subview.sizeThatFits(.unspecified)

            if point.x + size.width > bounds.maxX {
                point.x = bounds.origin.x
                point.y += lineHeight + spacing
                lineHeight = 0
            }

            subview.place(at: point, proposal: .unspecified)
            point.x += size.width + spacing
            lineHeight = max(lineHeight, size.height)
        }
    }
}

// Usage
FlowLayout(spacing: 12) {
    ForEach(tags) { tag in
        TagView(tag: tag)
    }
}

With Cache

struct CachedLayout: Layout {
    struct CacheData {
        var sizes: [CGSize] = []
    }

    func makeCache(subviews: Subviews) -> CacheData {
        CacheData(sizes: subviews.map { $0.sizeThatFits(.unspecified) })
    }

    func sizeThatFits(proposal: ProposedViewSize, subviews: Subviews, cache: inout CacheData) -> CGSize {
        // Use cache.sizes instead of measuring again
    }
}

Layout Values

// Define custom layout value
struct Rank: LayoutValueKey {
    static let defaultValue: Int = 0
}

extension View {
    func rank(_ value: Int) -> some View {
        layoutValue(key: Rank.self, value: value)
    }
}

// Read in layout
func placeSubviews(...) {
    let sorted = subviews.sorted { $0[Rank.self] < $1[Rank.self] }
}

onGeometryChange

Efficient geometry reading without layout side effects. Backported to iOS 16+.

Basic Usage

@State private var size: CGSize = .zero

var body: some View {
    content
        .onGeometryChange(for: CGSize.self) { proxy in
            proxy.size
        } action: { newSize in
            size = newSize
        }
}

Reading Specific Values

// Width only
.onGeometryChange(for: CGFloat.self) { proxy in
    proxy.size.width
} action: { width in
    columnCount = max(1, Int(width / 150))
}

// Frame in coordinate space
.onGeometryChange(for: CGRect.self) { proxy in
    proxy.frame(in: .global)
} action: { frame in
    globalFrame = frame
}

// Aspect ratio
.onGeometryChange(for: Bool.self) { proxy in
    proxy.size.width > proxy.size.height
} action: { isWide in
    self.isWide = isWide
}

Coordinate Spaces

// Named coordinate space
ScrollView {
    content
        .onGeometryChange(for: CGFloat.self) { proxy in
            proxy.frame(in: .named("scroll")).minY
        } action: { offset in
            scrollOffset = offset
        }
}
.coordinateSpace(name: "scroll")

Comparison with GeometryReader

AspectonGeometryChangeGeometryReader
Layout impactNoneGreedy (fills space)
When evaluatedAfter layoutDuring layout
Use caseSide effectsLayout calculations
iOS version16+ (backported)13+

GeometryReader

Provides geometry information during layout phase. Use sparingly due to greedy sizing.

Basic Usage (Constrained)

// ✅ Always constrain GeometryReader
GeometryReader { proxy in
    let width = proxy.size.width
    HStack(spacing: 0) {
        Rectangle().frame(width: width * 0.3)
        Rectangle().frame(width: width * 0.7)
    }
}
.frame(height: 100)  // Required constraint

GeometryProxy Properties

GeometryReader { proxy in
    // Container size
    let size = proxy.size  // CGSize

    // Safe area insets
    let insets = proxy.safeAreaInsets  // EdgeInsets

    // Frame in coordinate space
    let globalFrame = proxy.frame(in: .global)
    let localFrame = proxy.frame(in: .local)
    let namedFrame = proxy.frame(in: .named("container"))
}

Common Patterns

// Proportional sizing
GeometryReader { geo in
    VStack {
        header.frame(height: geo.size.height * 0.2)
        content.frame(height: geo.size.height * 0.8)
    }
}

// Centering with offset
GeometryReader { geo in
    content
        .position(x: geo.size.width / 2, y: geo.size.height / 2)
}

Avoiding Common Mistakes

// ❌ Unconstrained in VStack
VStack {
    GeometryReader { ... }  // Takes ALL space
    Button("Next") { }       // Invisible
}

// ✅ Constrained
VStack {
    GeometryReader { ... }
        .frame(height: 200)
    Button("Next") { }
}

// ❌ Causing layout loops
GeometryReader { geo in
    content
        .frame(width: geo.size.width)  // Can cause infinite loop
}

Safe Area Padding

SwiftUI 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).

The Critical Difference

// ❌ WRONG - Ignores safe areas, content hits notch/home indicator
ScrollView {
    content
}
.padding(.horizontal, 20)

// ✅ CORRECT - Respects safe areas, adds padding beyond them
ScrollView {
    content
}
.safeAreaPadding(.horizontal, 20)

Key insight: .padding() adds fixed spacing from the view's edges. .safeAreaPadding() adds spacing beyond the safe area insets.

When to Use Each

Use .padding() when
  • Adding spacing between sibling views within a container
  • Creating internal spacing that should be consistent everywhere
  • Working with views that already respect safe areas (like List, Form)
  • Adding decorative spacing on macOS (no safe area concerns)
VStack(spacing: 0) {
    header
        .padding(.horizontal, 16)  // ✅ Internal spacing

    Divider()

    content
        .padding(.horizontal, 16)  // ✅ Internal spacing
}
Use .safeAreaPadding() when (iOS 17+)
  • Adding margin to full-width content that extends to screen edges
  • Implementing edge-to-edge scrolling with proper insets
  • Creating custom containers that need safe area awareness
  • Working with Liquid Glass or full-screen materials
// ✅ Edge-to-edge list with custom padding
List(items) { item in
    ItemRow(item)
}
.listStyle(.plain)
.safeAreaPadding(.horizontal, 20)  // Adds 20pt beyond safe areas

// ✅ Full-screen content with proper margins
ZStack {
    Color.blue.ignoresSafeArea()

    VStack {
        content
    }
    .safeAreaPadding(.all, 16)  // Respects notch, home indicator
}

Platform Availability

iOS 17+, iPadOS 17+, macOS 14+, axiom-visionOS 1.0+

For earlier iOS versions, use manual safe area handling:

// iOS 13-16 fallback
GeometryReader { geo in
    content
        .padding(.horizontal, 20 + geo.safeAreaInsets.leading)
}

Or conditional compilation:

if #available(iOS 17, *) {
    content.safeAreaPadding(.horizontal, 20)
} else {
    content.padding(.horizontal, 20)
        .padding(.leading, safeAreaInsets.leading)
}

Edge-Specific Usage

// Top only (below status bar/notch)
.safeAreaPadding(.top, 8)

// Bottom only (above home indicator)
.safeAreaPadding(.bottom, 16)

// Horizontal (left/right of safe areas)
.safeAreaPadding(.horizontal, 20)

// All edges
.safeAreaPadding(.all, 16)

// Individual edges
.safeAreaPadding(EdgeInsets(top: 8, leading: 20, bottom: 16, trailing: 20))

Common Patterns

Edge-to-Edge ScrollView
ScrollView {
    LazyVStack(spacing: 12) {
        ForEach(items) { item in
            ItemCard(item)
        }
    }
}
.safeAreaPadding(.horizontal, 16)  // Content inset from edges + safe areas
.safeAreaPadding(.vertical, 8)
Full-Screen Background with Safe Content
ZStack {
    // Background extends edge-to-edge
    LinearGradient(...)
        .ignoresSafeArea()

    // Content respects safe areas + custom padding
    VStack {
        header
        Spacer()
        content
        Spacer()
        footer
    }
    .safeAreaPadding(.all, 20)
}
Nested Padding (Combined Approach)
// Outer: Safe area padding for device insets
VStack(spacing: 0) {
    content
}
.safeAreaPadding(.horizontal, 16)  // Beyond safe areas

// Inner: Regular padding for internal spacing
VStack {
    Text("Title")
        .padding(.bottom, 8)  // Internal spacing
    Text("Subtitle")
}

Decision Tree

Does your content extend to screen edges?
├─ YES → Use .safeAreaPadding()
│   ├─ Is it scrollable? → .safeAreaPadding(.horizontal/.vertical)
│   └─ Is it full-screen? → .safeAreaPadding(.all)
│
└─ NO (contained within a safe container like List/Form)
    └─ Use .padding() for internal spacing

Visual Debugging

// Visualize safe area padding (iOS 17+)
content
    .safeAreaPadding(.horizontal, 20)
    .background(.red.opacity(0.2))  // Shows padding area
    .border(.blue)  // Shows content bounds

Migration from Manual Safe Area Handling

// ❌ OLD: Manual calculation (iOS 13-16)
GeometryReader { geo in
    content
        .padding(.top, geo.safeAreaInsets.top + 16)
        .padding(.bottom, geo.safeAreaInsets.bottom + 16)
        .padding(.horizontal, 20)
}

// ✅ NEW: .safeAreaPadding() (iOS 17+)
content
    .safeAreaPadding(.vertical, 16)
    .safeAreaPadding(.horizontal, 20)

Related APIs

.safeAreaInset(edge:) - Adds persistent content that shrinks the safe area:

ScrollView {
    content
}
.safeAreaInset(edge: .bottom) {
    // This REDUCES the safe area, content scrolls under it
    toolbarButtons
        .padding()
        .background(.ultraThinMaterial)
}

.ignoresSafeArea() - Opts out of safe area completely:

Color.blue
    .ignoresSafeArea()  // Extends to absolute screen edges

Why It Matters

Before iOS 17: Developers had to manually calculate safe area insets with GeometryReader, leading to:

  • Verbose code
  • Performance overhead (GeometryReader forces extra layout pass)
  • Easy mistakes (forgetting to check all edges)

iOS 17+: .safeAreaPadding() provides:

  • Declarative API (matches SwiftUI philosophy)
  • Automatic safe area awareness
  • Better performance (no extra layout passes)
  • Type-safe edge specification

Real-world impact: Using .padding() instead of .safeAreaPadding() on iPhone 15 Pro causes content to:

  • Hit the Dynamic Island (top)
  • Overlap the home indicator (bottom)
  • Get cut off by screen corners (rounded edges)

Size Classes

Environment values indicating horizontal and vertical size characteristics.

Reading Size Classes

struct AdaptiveView: View {
    @Environment(\.horizontalSizeClass) var horizontalSizeClass
    @Environment(\.verticalSizeClass) var verticalSizeClass

    var body: some View {
        if horizontalSizeClass == .compact {
            compactLayout
        } else {
            regularLayout
        }
    }
}

Size Class Values

enum UserInterfaceSizeClass {
    case compact    // Constrained space
    case regular    // Ample space
}

Platform Behavior

iPhone:

OrientationHorizontalVertical
Portrait.compact.regular
Landscape (small).compact.compact
Landscape (Plus/Max).regular.compact

iPad:

ConfigurationHorizontalVertical
Any full screen.regular.regular
70% Split View.regular.regular
50% Split View.regular.regular
33% Split View.compact.regular
Slide Over.compact.regular

Overriding Size Classes

content
    .environment(\.horizontalSizeClass, .compact)

Dynamic Type Size

Environment value for user's preferred text size.

Reading Dynamic Type

@Environment(\.dynamicTypeSize) var dynamicTypeSize

var body: some View {
    if dynamicTypeSize.isAccessibilitySize {
        accessibleLayout
    } else {
        standardLayout
    }
}

Size Categories

enum DynamicTypeSize: Comparable {
    case xSmall
    case small
    case medium
    case large           // Default
    case xLarge
    case xxLarge
    case xxxLarge
    case accessibility1  // isAccessibilitySize = true
    case accessibility2
    case accessibility3
    case accessibility4
    case accessibility5
}

Scaled Metric

@ScaledMetric var iconSize: CGFloat = 24
@ScaledMetric(relativeTo: .largeTitle) var headerSize: CGFloat = 44

Image(systemName: "star")
    .frame(width: iconSize, height: iconSize)

iOS 26 Window APIs

Window Resize Anchor

WindowGroup {
    ContentView()
}
.windowResizeAnchor(.topLeading)  // Resize originates from top-left
.windowResizeAnchor(.center)      // Resize from center

Menu Bar Commands (iPad)

@main
struct MyApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .commands {
            CommandMenu("View") {
                Button("Show Sidebar") {
                    showSidebar.toggle()
                }
                .keyboardShortcut("s", modifiers: [.command, .option])

                Divider()

                Button("Zoom In") { zoom += 0.1 }
                    .keyboardShortcut("+")
                Button("Zoom Out") { zoom -= 0.1 }
                    .keyboardShortcut("-")
            }
        }
    }
}

NavigationSplitView Column Control

// iOS 26: Automatic column visibility
NavigationSplitView {
    Sidebar()
} content: {
    ContentList()
} detail: {
    DetailView()
}
// Columns auto-hide/show based on available width

// Manual control (when needed)
@State private var columnVisibility: NavigationSplitViewVisibility = .all

NavigationSplitView(columnVisibility: $columnVisibility) {
    Sidebar()
} detail: {
    DetailView()
}

Scene Phase

@Environment(\.scenePhase) var scenePhase

var body: some View {
    content
        .onChange(of: scenePhase) { oldPhase, newPhase in
            switch newPhase {
            case .active:
                // Window is visible and interactive
            case .inactive:
                // Window is visible but not interactive
            case .background:
                // Window is not visible
            }
        }
}

Coordinate Spaces

Built-in Coordinate Spaces

// Global (screen coordinates)
proxy.frame(in: .global)

// Local (view's own bounds)
proxy.frame(in: .local)

// Named (custom)
proxy.frame(in: .named("mySpace"))

Creating Named Spaces

ScrollView {
    content
        .onGeometryChange(for: CGFloat.self) { proxy in
            proxy.frame(in: .named("scroll")).minY
        } action: { offset in
            scrollOffset = offset
        }
}
.coordinateSpace(name: "scroll")

// iOS 17+ typed coordinate space
extension CoordinateSpaceProtocol where Self == NamedCoordinateSpace {
    static var scroll: Self { .named("scroll") }
}

ScrollView Geometry (iOS 18+)

onScrollGeometryChange

ScrollView {
    content
}
.onScrollGeometryChange(for: CGFloat.self) { geometry in
    geometry.contentOffset.y
} action: { offset in
    scrollOffset = offset
}

ScrollGeometry Properties

.onScrollGeometryChange(for: ScrollGeometry.self) { $0 } action: { geo in
    let offset = geo.contentOffset      // Current scroll position
    let size = geo.contentSize          // Total content size
    let visible = geo.visibleRect       // Currently visible rect
    let insets = geo.contentInsets      // Content insets
}

Lazy Container Gotchas

Recycling Behavior

LazyVStack and LazyHStack create views on demand and recycle them when off-screen. This means:

  • View identity matters: If cells flash/disappear during fast scrolling, the view identity is unstable. Use explicit .id() on items.
  • onAppear/onDisappear fire repeatedly: Views are created and destroyed as you scroll. Don't use these for one-time setup.
  • State resets on recycle: @State in lazy items resets when recycled. Lift state to the model layer.
// ❌ Items flash during fast scroll — unstable identity
LazyVStack {
    ForEach(Array(items.enumerated()), id: \.offset) { index, item in
        ItemRow(item: item)  // Identity changes when array mutates
    }
}

// ✅ Stable identity prevents flash/disappear
LazyVStack {
    ForEach(items) { item in  // Uses item.id (Identifiable)
        ItemRow(item: item)
    }
}

When NOT to Use Lazy Containers

ScenarioUse InsteadWhy
< 50 itemsVStack / HStackNo recycling overhead, simpler
Nested in another lazy containerVStack (inner)Nested lazy causes layout issues
Need all items measured upfrontVStackLazy containers don't know total size

Resources

WWDC: 2025-208, 2024-10074, 2022-10056

Docs: /swiftui/layout, /swiftui/viewthatfits

Skills: axiom-swiftui-layout, axiom-swiftui-debugging

平台分布

Claude Code

25.84%
按下载量换算400

Codex

24.13%
按下载量换算374

OpenCode

18.99%
按下载量换算294

Antigravity

11.39%
按下载量换算176

Cursor

6.76%
按下载量换算105

Gemini CLI

3.28%
按下载量换算51

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。

继续浏览同类 Skills