关于 swiftui-layout-components
面向 SwiftUI 开发者的布局与组件模式技能,涵盖列表、滚动视图、表单、搜索和覆盖层,并提供容器选择与常见错误指导。
主要功能
- 01栈与网格布局模式
- 02列表和滚动视图模式
- 03表单、控件与搜索
- 04覆盖层和呈现指导
- 05性能与常见错误检查
适用场景
- 01为大型集合选择惰性布局
- 02构建设置表单和输入界面
- 03为列表添加搜索与滚动行为
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dpearson2699/ swift-ios-skills --skill swiftui-layout-components安装后在 Claude Code 中输入/swiftui-layout-components确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
evals/evals.jsonreferences/form.mdgrids.mdlist.mdscrollview.md详细介绍
SwiftUI 布局与组件
面向 iOS 26+ 和 Swift 6.3 的 SwiftUI 应用布局与组件模式。涵盖堆栈和网格布局、列表模式、滚动视图、表单、控件、搜索和覆盖层。除非另有说明,这些模式向后兼容 iOS 17。
目录
布局基础
标准堆栈
小型、固定尺寸的内容使用 VStack、HStack 和 ZStack。它们会立即渲染所有子视图。
VStack(alignment: .leading) {
Text(title).font(.headline)
Text(subtitle).font(.subheadline).foregroundStyle(.secondary)
}
延迟堆栈
在 ScrollView 中使用 LazyVStack 和 LazyHStack 来展示大型或动态集合。子视图滚动进入可见区域时,它们才会按需创建。
ScrollView {
LazyVStack {
ForEach(items) { item in
ItemRow(item: item)
}
}
.padding(.horizontal)
}
如何选择:
- 非延迟堆栈: 小型、固定内容(标题、工具栏、字段较少的表单)
- 延迟堆栈: 大型或大小未知的集合、信息流、聊天消息
网格布局
使用 LazyVGrid 创建图标选择器、媒体库和密集的视觉选项。使用 .adaptive 列来构建可适应不同设备尺寸的布局,或使用 .flexible 列来固定列数。
// Adaptive grid -- columns adjust to fit
let columns = [GridItem(.adaptive(minimum: 120, maximum: 1024))]
LazyVGrid(columns: columns) {
ForEach(items) { item in
ThumbnailView(item: item)
.aspectRatio(1, contentMode: .fit)
}
}
// Fixed 3-column grid
let columns = Array(repeating: GridItem(.flexible(minimum: 100), spacing: 4), count: 3)
LazyVGrid(columns: columns, spacing: 4) {
ForEach(items) { item in
ThumbnailView(item: item)
}
}
使用 .aspectRatio 设置单元格尺寸。切勿在延迟容器中放置 GeometryReader——它会强制进行急切测量,抵消延迟加载的效果。如果需要读取尺寸,请使用 .onGeometryChange(iOS 16+)。
完整的网格模式和设计选择请参阅 references/grids.md。
列表模式
当内置的行复用、选择和辅助功能很重要时,请使用 List 展示信息流式内容和设置行。
List {
Section("General") {
NavigationLink("Display") { DisplaySettingsView() }
NavigationLink("Haptics") { HapticsSettingsView() }
}
Section("Account") {
Button("Sign Out", role: .destructive) { }
}
}
.listStyle(.insetGrouped)
关键模式:
- 信息流布局使用
.listStyle(.plain),设置页面使用.insetGrouped - 使用
.scrollContentBackground(.hidden)+ 自定义背景来创建主题化表面 - 使用
.listRowInsets(...)和.listRowSeparator(.hidden)控制间距和分隔线 - 边缘滚动: 使用
List+ScrollPosition,并配合.scrollPosition($scrollPosition)实现滚动到顶部或底部的操作 - 跳转到项目或分区: 使用
ScrollView+ 延迟堆栈,配合.scrollTargetLayout()和稳定的目标,实现可靠的按 ID 跳转 - 使用
.refreshable { }为信息流添加下拉刷新 - 对需要整行均可点击的行使用
.contentShape(Rectangle()) - 对于布局审查或迁移指导,先说明容器选择和约束;代码片段保持简短,弹簧、转场和时序选择留给
swiftui-animation
iOS 26: 使用 .scrollEdgeEffectStyle(.soft, for: .top) 添加现代滚动边缘效果。
包括带滚动到顶部功能的信息流列表在内的完整列表模式,请参阅 references/list.md。
ScrollView
需要自定义布局、混合内容或水平滚动时,请将 ScrollView 与延迟堆栈搭配使用。
ScrollView(.horizontal, showsIndicators: false) {
LazyHStack {
ForEach(chips) { chip in
ChipView(chip: chip)
}
}
}
ScrollPosition: 支持以声明式双向跟踪滚动位置并以编程方式滚动。
@State private var scrollPosition = ScrollPosition(edge: .bottom)
ScrollView {
LazyVStack {
ForEach(messages) { message in
MessageRow(message: message)
}
}
.scrollTargetLayout()
}
.scrollPosition($scrollPosition)
.onChange(of: messages.last?.id) {
withAnimation { scrollPosition.scrollTo(edge: .bottom) }
}
safeAreaInset(edge:) 可将内容(输入栏、工具栏)固定在键盘上方,且不会影响滚动布局。
iOS 26 新增内容:
.scrollEdgeEffectStyle(.soft, for: .top)——淡出边缘效果.backgroundExtensionEffect()——在安全区域边缘进行镜像/模糊处理(谨慎使用,每个屏幕仅用一次).safeAreaBar(edge:)——添加与滚动效果集成的栏视图
完整的 ScrollPosition、分页显示、缩放/裁剪冲突和 iOS 26 边缘效果模式,请参阅 references/scrollview.md。
表单与控件
Form
使用 Form 构建结构化设置和输入页面。将相关控件分组到 Section 区块中。
Form {
Section("Notifications") {
Toggle("Mentions", isOn: $prefs.mentions)
Toggle("Follows", isOn: $prefs.follows)
}
Section("Appearance") {
Picker("Theme", selection: $theme) {
ForEach(Theme.allCases, id: \.self) { Text($0.title).tag($0) }
}
Slider(value: $fontScale, in: 0.5...1.5, step: 0.1)
}
}
.formStyle(.grouped)
.scrollContentBackground(.hidden)
使用 @FocusState 管理输入密集型表单中的键盘焦点。仅在独立展示或以工作表形式展示时,才使用 NavigationStack 包裹。
控件
| 控件 | 用法 |
|---|---|
Toggle | 布尔偏好设置 |
Picker | 离散选项;对于 2-4 个选项,使用 .segmented |
Slider | 带有可见数值标签的数值范围 |
DatePicker | 日期/时间选择 |
TextField | 文本输入,支持 .keyboardType、.textInputAutocapitalization |
将控件直接绑定到 @State、@Binding 或 @AppStorage。使用 Form 分区对相关控件分组。使用 .disabled(...) 反映已锁定或继承的设置。如果有助于表达清晰,可在切换控件中使用 Label 组合图标和文本。
对于大量选项,避免使用 .pickerStyle(.segmented);改用菜单或内联样式。不要隐藏滑块标签;始终显示上下文。
完整表单示例请参阅 references/form.md。
可搜索界面
使用 .searchable 添加原生搜索界面。对于多种模式,使用 .searchScopes;对于防抖后的异步结果,使用 .task(id:)。
@MainActor
struct ExploreView: View {
@State private var searchQuery = ""
@State private var searchScope: SearchScope = .all
@State private var isSearching = false
@State private var results: [SearchResult] = []
var body: some View {
List {
if isSearching {
ProgressView()
} else {
ForEach(results) { result in
SearchRow(result: result)
}
}
}
.searchable(
text: $searchQuery,
placement: .navigationBarDrawer(displayMode: .always),
prompt: Text("Search")
)
.searchScopes($searchScope) {
ForEach(SearchScope.allCases, id: \.self) { scope in
Text(scope.title)
}
}
.task(id: searchQuery) {
await runSearch()
}
}
private func runSearch() async {
guard !searchQuery.isEmpty else {
results = []
return
}
isSearching = true
defer { isSearching = false }
try? await Task.sleep(for: .milliseconds(250))
results = await fetchResults(query: searchQuery, scope: searchScope)
}
}
搜索内容为空时显示占位提示。对输入进行防抖处理,避免过度请求。将搜索状态保留在视图本地。避免针对空字符串执行搜索。
覆盖层与展示
使用 .overlay(alignment:) 显示临时 UI(Toast、横幅),而不影响布局。
struct AppRootView: View {
@State private var toast: Toast?
var body: some View {
content
.overlay(alignment: .top) {
if let toast {
ToastView(toast: toast)
.transition(.move(edge: .top).combined(with: .opacity))
.onAppear {
Task {
try? await Task.sleep(for: .seconds(2))
withAnimation { self.toast = nil }
}
}
}
}
}
}
临时 UI 优先使用覆盖层,而不是嵌入布局堆栈。使用过渡效果和较短的自动消失计时器。将覆盖层对齐到明确的边缘(.top 或 .bottom)。除非确有需要,否则避免使用会阻止所有交互的覆盖层。不要叠加过多覆盖层;使用队列或替换当前 Toast。
对于模态路由、工作表停靠点和全屏展示策略,请交由 swiftui-navigation 技能处理。
常见错误
- 在惰性容器中放置
GeometryReader会破坏惰性加载;需要尺寸时请使用.onGeometryChange。 - 数组索引会导致
ForEachID 不稳定,并产生错误的差异计算。 - 同一轴向的嵌套滚动视图会造成手势冲突。
- 复杂的自定义行或可展开的
List行应放在ScrollView+LazyVStack中。 - 大量选项应使用菜单或内联选择器样式,而不是
.segmented。 - 除非有意设置特定间距,否则应省略堆栈/网格的
spacing:,以采用平台自适应的默认值。 - 使用单个归一化进度值驱动滚动显示效果,不要使用并行布尔值或重复的拖动手势。
- 将每帧滚动几何信息保留在本地,并避免更改用于计算进度的几何信息。
审查清单
- 对大型或动态集合使用
LazyVStack/LazyHStack - 所有
ForEach项目都有稳定的IdentifiableID(而非数组索引) - 惰性容器内没有
GeometryReader -
List样式符合上下文(信息流使用.plain,设置使用.insetGrouped) - 结构化输入界面使用
Form(而非自定义堆栈) -
.searchable使用.task(id:)对输入进行防抖处理 - 数据源支持下拉刷新时添加
.refreshable - 覆盖层使用过渡效果和自动消失计时器
- 可点击的行使用
.contentShape(Rectangle()) - 表单中使用
@FocusState管理键盘焦点 - 除非需要特定值,否则省略堆栈/网格的
spacing: - 滚动驱动的显示效果使用单个归一化进度值,并将几何信息更新限制在较小的子树中
- 缩放/裁剪交互冲突时禁用滚动;离散的可见性效果不驱动连续动画
参考资料
- 网格模式:references/grids.md
- 列表和分区模式:references/list.md
- ScrollView 和惰性堆栈:references/scrollview.md
- 表单模式:references/form.md
- 架构和状态管理:参阅
swiftui-patterns技能 - 导航模式:参阅
swiftui-navigation技能