SwiftUI 界面与布局(组件手册)
按使用频率讲解布局、控件、修饰符;既能顺着学,也能当 API 速查。
1怎么用这一章(教程 + API 手册)
先按顺序过一遍「高频」组件,能拼出大多数界面;以后忘了参数,用侧栏搜索或本页目录跳到对应卡片当 API 查。
| 想做… | 先看 | 频率 |
|---|---|---|
| 竖/横/叠布局 | 布局容器 | 常用 |
| 标题、正文、图标 | Text / Image | 常用 |
| 按钮、输入、开关 | 控件 | 常用 |
| 边距、圆角、颜色 | 修饰符速查 | 常用 |
| 列表 / 导航 | 本章点到 + 第 07 章 | 常用 |
| 表单、选择器 | Form / Picker | 常用 |
| 网格、懒加载栈 | Lazy* / Grid | 偶尔 |
2布局容器(完整参数手册)
下面按「初始化参数 → 每个取值含义 → 示例」写全,方便当 API 查。90% 界面仍是三大 Stack + Spacer。
VStack 常用子视图从上到下排列。页面骨架、表单区块、标题+正文最常用。≈ Compose Column / ArkUI Column。
VStack(
alignment: HorizontalAlignment = .center,
spacing: CGFloat? = nil,
@ViewBuilder content: () -> Content
)
| 参数 | 类型 | 默认 | 含义 |
|---|---|---|---|
alignment | HorizontalAlignment | .center | 子视图在水平方向如何对齐(因为竖排时,左右才是「对齐轴」) |
spacing | CGFloat? | nil | 相邻子视图之间的竖直间距(单位 pt)。nil = 用系统默认间距;写 0 则紧贴;写 12 则固定 12pt |
content | @ViewBuilder 闭包 | (必填) | 花括号 { ... } 里放子视图,可写多个 |
alignment 全部取值(HorizontalAlignment)| 取值 | 效果(LTR 中文/英文界面) | 什么时候用 |
|---|---|---|
.leading | 子视图整体靠左(前缘对齐) | 标题+正文、表单标签,最常用 |
.center | 子视图水平居中(默认) | 欢迎页、空状态、居中按钮组 |
.trailing | 子视图整体靠右(后缘对齐) | 右对齐金额、操作区 |
VStack(alignment: .leading, spacing: 12) {
Text("标题").font(.title.bold())
Text("副标题").foregroundStyle(.secondary)
Button("开始") { }
}
.padding()
// spacing: nil → 系统默认间距
VStack { Text("A"); Text("B") }
// spacing: 0 → 紧贴
VStack(spacing: 0) { Text("A"); Text("B") }
HStack 常用子视图从左到右排列。图标+文字、底栏、一行信息。≈ Compose Row / ArkUI Row。
HStack(
alignment: VerticalAlignment = .center,
spacing: CGFloat? = nil,
@ViewBuilder content: () -> Content
)
| 参数 | 类型 | 默认 | 含义 |
|---|---|---|---|
alignment | VerticalAlignment | .center | 子视图在竖直方向如何对齐(横排时,上下才是对齐轴) |
spacing | CGFloat? | nil | 相邻子视图之间的水平间距。nil 默认;0 紧贴;8 固定 8pt |
content | @ViewBuilder | (必填) | 花括号里的子视图 |
alignment 全部取值(VerticalAlignment)| 取值 | 效果 | 什么时候用 |
|---|---|---|
.top | 子视图顶部对齐 | 左边大图、右边多行文字时,文字贴顶 |
.center | 垂直居中(默认) | 图标和单行文字并排,最常用 |
.bottom | 子视图底部对齐 | 底部基线对齐的装饰 |
.firstTextBaseline | 按第一行文字基线对齐 | 不同字号的 Text 并排,要「字坐在同一条线上」 |
.lastTextBaseline | 按最后一行文字基线对齐 | 多行文本与旁边元素底对齐(少用) |
HStack(alignment: .center, spacing: 8) {
Image(systemName: "star.fill")
Text("收藏")
Spacer() // 把右边推开,见 Spacer
Text("12")
}
// 大标题 + 小标注:用基线对齐更整齐
HStack(alignment: .firstTextBaseline, spacing: 4) {
Text("¥99").font(.largeTitle.bold())
Text("起").font(.caption).foregroundStyle(.secondary)
}
ZStack 常用子视图叠在一起(Z 轴)。写在后面的在上层。典型:背景+内容、头像角标、卡片底+文字。≈ Compose Box / ArkUI Stack。
ZStack(
alignment: Alignment = .center,
@ViewBuilder content: () -> Content
)
注意:ZStack 没有 spacing 参数(叠放不谈间距;要用 padding/offset 自己调)。
| 参数 | 类型 | 默认 | 含义 |
|---|---|---|---|
alignment | Alignment | .center | 所有子视图在 ZStack 区域内的默认停靠位置。某个子视图还可用自己的 .frame(alignment:) / padding 微调 |
content | @ViewBuilder | (必填) | 花括号里的子视图;越靠后越在上 |
alignment 全部取值(Alignment)——九宫格可以想成手机屏幕上的九个停靠点(中文 LTR):
| 取值 | 位置 | 常见用途 |
|---|---|---|
.topLeading | 上 + 左(左上角) | 关闭按钮、返回附近的角标 |
.top | 上 + 水平居中 | 顶部横幅、顶部标题条 |
.topTrailing | 上 + 右(右上角) | NEW / 红点角标(高频) |
.leading | 竖直居中 + 左 | 左侧竖条、左边装饰 |
.center | 正中(默认) | 背景上居中放主内容 |
.trailing | 竖直居中 + 右 | 右侧箭头、右侧操作 |
.bottomLeading | 下 + 左(左下角) | 左下说明文字 |
.bottom | 下 + 水平居中 | 底部指示器、底部按钮条 |
.bottomTrailing | 下 + 右(右下角) | 悬浮加号、右下角角标 |
alignment 管的是「默认停在哪一角」;真正把角标挪开一点,通常再给角标加 .padding(8)。// 右上角角标:alignment 用 .topTrailing
ZStack(alignment: .topTrailing) {
// 下层:背景(先写)
RoundedRectangle(cornerRadius: 12)
.fill(.blue.opacity(0.15))
.frame(height: 120)
// 中层:主内容
Text("卡片内容")
.padding()
// 上层:角标(后写 → 在最上面)
Text("NEW")
.font(.caption2.bold())
.padding(6)
.background(.red)
.foregroundStyle(.white)
.clipShape(Capsule())
.padding(8) // 别贴死边缘
}
// 默认居中叠放
ZStack {
Circle().fill(.orange).frame(width: 80, height: 80)
Text("42").bold().foregroundStyle(.white)
}
Spacer 常用在 Stack 里贪婪占据剩余空间,把兄弟视图推到两边/两端。
Spacer(minLength: CGFloat? = nil)
| 参数 | 类型 | 默认 | 含义 |
|---|---|---|---|
minLength | CGFloat? | nil | Spacer 被压缩时的最小长度。nil 时系统用默认最小(可接近 0);写 8 表示再挤也不能小于 8pt |
- 在
HStack里:水平方向撑开(左内容 | Spacer | 右内容) - 在
VStack里:竖直方向撑开(上内容 / Spacer / 下内容) - 在
ZStack里:一般没什么用(叠放不分配轴上剩余空间) - 多个 Spacer:大致均分剩余空间
HStack {
Text("左")
Spacer() // 尽量撑开
Text("右")
}
HStack {
Text("左")
Spacer(minLength: 24) // 最少留 24pt 缝
Text("右")
}
Divider 常用画一条系统风格的细分割线。
Divider()
无初始化参数。外观靠修饰符或环境间接影响:
| 常见改法 | 作用 |
|---|---|
| (默认) | 跟随系统分隔线颜色 |
.background(.red) 等(效果因系统版本而异,不如 overlay 直观) | 尝试改色 |
自制:Rectangle().fill(.quaternary).frame(height: 1) | 完全自定义细线 |
VStack {
Text("上")
Divider()
Text("下")
}
ScrollView 常用内容可能超出屏幕,需要滚动查看。
ScrollView(
_ axes: Axis.Set = .vertical,
showsIndicators: Bool = true,
@ViewBuilder content: () -> Content
)
| 参数 | 类型 | 默认 | 含义 |
|---|---|---|---|
axes | Axis.Set | .vertical | 允许滚动的方向,见下表 |
showsIndicators | Bool | true | 是否显示滚动条指示器。false = 隐藏(仍可滚) |
content | @ViewBuilder | (必填) | 被滚动的内容 |
axes 全部常用取值| 取值 | 含义 |
|---|---|
.vertical | 只能上下滚(默认);可省略不写 |
.horizontal | 只能左右滚 |
[.horizontal, .vertical] 或 .all(视版本写法) | 两个方向都能滚(少用,注意嵌套手势冲突) |
ScrollView { // 默认竖直
VStack { /* 很长内容 */ }
}
ScrollView(.horizontal, showsIndicators: false) {
HStack { /* 横向卡片 */ }
}
List / LazyVStack,不要 ScrollView + VStack 一次创建全部。LazyVStack / LazyHStack 偶尔和 VStack/HStack 排列方式相同,但懒加载:只创建靠近可见区域的子视图。必须放在可滚动容器(通常是 ScrollView)里。
LazyVStack(
alignment: HorizontalAlignment = .center,
spacing: CGFloat? = nil,
pinnedViews: PinnedScrollableViews = .init(),
@ViewBuilder content: () -> Content
)
LazyHStack(
alignment: VerticalAlignment = .center,
spacing: CGFloat? = nil,
pinnedViews: PinnedScrollableViews = .init(),
@ViewBuilder content: () -> Content
)
| 参数 | 类型 | 默认 | 含义 |
|---|---|---|---|
alignment | 同 VStack / HStack | .center | 对齐方式;取值表见上文 VStack/HStack |
spacing | CGFloat? | nil | 子视图间距 |
pinnedViews | PinnedScrollableViews | 空 | 滚动时是否钉住 Section 的 header/footer。常用:.sectionHeaders、.sectionFooters、或二者组合 |
content | @ViewBuilder | (必填) | 子视图;常配合 ForEach |
ScrollView {
LazyVStack(alignment: .leading, spacing: 8, pinnedViews: [.sectionHeaders]) {
ForEach(0..<200, id: \.self) { i in
Text("行 \(i)").padding()
}
}
}
LazyVGrid / LazyHGrid 偶尔网格(宫格、相册)。竖向滚动网格用 LazyVGrid;横向用 LazyHGrid。
LazyVGrid(
columns: [GridItem],
alignment: HorizontalAlignment = .center,
spacing: CGFloat? = nil,
pinnedViews: PinnedScrollableViews = .init(),
@ViewBuilder content: () -> Content
)
| 参数 | 类型 | 默认 | 含义 |
|---|---|---|---|
columns(V)/ rows(H) | [GridItem] | (必填) | 列(或行)怎么分,见下表 GridItem |
alignment | 水平/垂直对齐 | .center | 格子内容对齐 |
spacing | CGFloat? | nil | 行与行(或列与列)间距 |
pinnedViews | PinnedScrollableViews | 空 | 是否钉住 section 头/尾 |
content | @ViewBuilder | (必填) | 每个格子的内容 |
GridItem 尺寸模式(写全)| 写法 | 含义 |
|---|---|
GridItem(.flexible(minimum:maximum:)) | 弹性列:在最小~最大之间平分剩余宽度。只写 .flexible() 最常见 |
GridItem(.adaptive(minimum:maximum:)) | 自适应:按 minimum 自动算能排几列(宫格常用) |
GridItem(.fixed(80)) | 固定 80pt 宽(或高) |
GridItem(..., spacing: 8, alignment: .center) | 该项与下一项的间距、格子内对齐 |
// 固定 3 列
let three = Array(repeating: GridItem(.flexible(), spacing: 12), count: 3)
// 最小 100pt,能排几列排几列
let adaptive = [GridItem(.adaptive(minimum: 100), spacing: 12)]
ScrollView {
LazyVGrid(columns: adaptive, spacing: 12) {
ForEach(0..<12, id: \.self) { i in
RoundedRectangle(cornerRadius: 8)
.fill(.blue.opacity(0.2))
.frame(height: 80)
.overlay(Text("\(i)"))
}
}
.padding()
}
3Text 与字体
Text 常用展示只读文字(用户不能直接编辑;要输入用 TextField)。最常见写法:
Text("你好")—— 固定文案Text("你好,\(name)")—— 字符串插值,把变量嵌进文字
下面每一项都是「点在 Text 后面」的链式修饰符:改字体、颜色、行数等。不写则用系统默认。
.font(...) —— 字体大小 / 样式档位
决定字有多大、像不像标题。系统提供语义档位(跟动态字体、无障碍缩放兼容),也支持写死字号。
| 写法 | 什么意思 | 典型场景 |
|---|---|---|
.font(.largeTitle) | 最大号标题 | 欢迎页大标题 |
.font(.title) / .title2 / .title3 | 一级~三级标题,依次略小 | 页面标题、卡片标题 |
.font(.headline) | 偏粗的强调正文 | 列表行主标题 |
.font(.body) | 默认正文字号(不写 font 时也接近这个) | 普通说明文字 |
.font(.callout) / .subheadline | 略小于正文 | 次要说明 |
.font(.footnote) / .caption / .caption2 | 更小,caption2 最小档之一 | 角标、时间戳、辅助信息 |
.font(.system(size: 18)) | 写死 18pt(不太跟系统动态字体走) | 必须精确对齐设计稿时 |
.font(.system(size: 18, weight: .semibold, design: .rounded)) | 字号 + 粗细 + 字形风格(圆体等) | 品牌感强的数字/标题 |
.title / .body / .caption 这类语义档,少写死 size:,这样用户开「更大字体」时你的 App 也更跟手。.fontWeight(.bold) / .bold() —— 加粗
只改笔画粗细,不改字号档位。
.bold():快捷写法,等于粗体,最常用.fontWeight(.regular):常规.fontWeight(.medium)/.semibold/.bold/.heavy:越来越粗
也可以和 font 写在一起:.font(.title.bold()) 或 .font(.title).bold(),效果都是「标题档 + 加粗」。
Text("普通")
Text("加粗").bold()
Text("半粗").fontWeight(.semibold)
Text("标题加粗").font(.title.bold())
.foregroundStyle(...) —— 文字颜色 / 前景样式
控制字「涂什么色」。老 API 叫 .foregroundColor,新项目用 foregroundStyle。
| 写法 | 什么意思 |
|---|---|
.foregroundStyle(.primary) | 主文字色(浅色模式接近黑,深色模式接近白),默认观感 |
.foregroundStyle(.secondary) | 次要灰字,用来写说明、弱化信息 |
.foregroundStyle(.tertiary) | 更淡,辅助信息 |
.foregroundStyle(.red) / .blue 等 | 系统色 |
.foregroundStyle(Color(red:0, green:0.4, blue:1)) | 自定义 RGB |
.foregroundStyle(.white) | 白字(常配深色背景) |
Text("主标题") // 默认 primary
Text("说明文字").foregroundStyle(.secondary)
Text("错误").foregroundStyle(.red)
.multilineTextAlignment(...) —— 多行时怎么对齐
只影响多行 Text内部各行的对齐(单行时几乎看不出差别)。
.leading:靠前(中文/英文 LTR 下是左对齐)——说明文字最常用.center:居中——空状态、欢迎语常用.trailing:靠后(右对齐)——较少
VStack(alignment: .leading) 或 .frame(maxWidth: .infinity, alignment: .leading)。Text("这是一段比较长的说明,会自动换行。换行后每一行都左对齐。")
.multilineTextAlignment(.leading)
.frame(width: 200) // 限制宽度才会换行,方便看出效果
.lineLimit(...) —— 最多显示几行
限制文本最多占几行;超出部分会被截断(默认末尾 …)。
.lineLimit(1):只显示一行(列表副标题常用).lineLimit(2):最多两行.lineLimit(nil)或不写:不限制,有多少行显示多少行.lineLimit(1...3):至少 1 行、最多 3 行(较新系统)
Text(longArticle)
.lineLimit(2) // 再长也只显示两行,后面变省略号
.truncationMode(...) —— 超长时省略号放哪
和 lineLimit 一起用:决定「砍掉」的位置。
.tail(最常用):省略号在末尾 →很长的标题….middle:省略号在中间 →很长…标题(文件名有时用).head:省略号在开头 →…很长的标题(少见)
Text("SwiftUI界面与布局完全入门指南")
.lineLimit(1)
.truncationMode(.tail) // 末尾 …
// .truncationMode(.middle) // 中间 …
VStack(alignment: .leading, spacing: 8) {
// 大标题:大号 + 加粗
Text("今日待办")
.font(.title.bold())
// 说明:正文字号 + 灰色次要色 + 最多 3 行 + 多行左对齐
Text("这里是说明文字,可能会很长很长,超出三行会变成省略号。")
.font(.body)
.foregroundStyle(.secondary)
.multilineTextAlignment(.leading)
.lineLimit(3)
.truncationMode(.tail)
// 插值:把数字嵌进字符串;等宽数字更整齐
Text("未完成 \(3) 项")
.font(.title3.monospacedDigit())
.foregroundStyle(.primary)
}
.padding()
font(多大)→ 再 bold/fontWeight(多粗)→ 再 foregroundStyle(什么色)→ 需要换行/截断再加 multilineTextAlignment / lineLimit / truncationMode。Label 常用系统推荐的「图标+标题」组合,辅助功能更友好。
Label("标题", systemImage: "gear")
Label("标题", image: "AssetName") // Assets 图片
Label { Text("标题") } icon: { Image(...) } // 完全自定义
| 参数 | 含义 |
|---|---|
| 标题 | 显示的文字 |
systemImage | SF Symbol 名字(如 gearshape) |
image | 资源目录里的图片名 |
title / icon 闭包 | 自定义标题视图与图标视图 |
.labelStyle 全部常用取值| 取值 | 含义 |
|---|---|
.automatic | 随环境自动(默认) |
.titleAndIcon | 图标 + 标题都显示 |
.titleOnly | 只显示标题 |
.iconOnly | 只显示图标(工具栏常用) |
Label("设置", systemImage: "gearshape.fill")
Label("仅图标", systemImage: "heart.fill")
.labelStyle(.iconOnly)4Image 与 SF Symbols
Image 常用系统符号或资源图。新手优先 SF Symbols,免切图。
Image(systemName: "wifi") // SF Symbol
Image("photo") // Assets.xcassets 中的名字
Image(decorative: "photo") // 装饰图(辅助功能可忽略)
Image(uiImage: someUIImage) // 包一层 UIKit 图
| 修饰符 | 取值 / 说明 | 含义 |
|---|---|---|
.resizable(capInsets:resizingMode:) | 资源图必加,否则按原图像素显示 | 允许被 frame 拉伸/缩放 |
.scaledToFit() | — | 保持比例完整显示在框内(可能留边) |
.scaledToFill() | — | 填满框(可能裁切);常配 clipped() |
.imageScale | .small / .medium(默认)/ .large | SF Symbol 相对字号缩放 |
.symbolRenderingMode | .monochrome | 单色(默认观感) |
.hierarchical | 分层深浅 | |
.palette | 多色,配 foregroundStyle 多层色 | |
.multicolor | 使用符号自带多色 | |
.foregroundStyle | 颜色 | 给模板符号上色 |
.clipShape / .clipShape(Circle()) | 形状 | 裁成圆头像等 |
HStack(spacing: 16) {
Image(systemName: "wifi")
.imageScale(.large)
Image(systemName: "battery.100")
.foregroundStyle(.green)
Image("AppIcon") // Assets 里的名字;示例用系统符号即可
.resizable()
.scaledToFit()
.frame(width: 44, height: 44)
}5常用交互控件
Button 常用点击触发动作。几乎每个界面都有。
Button("标题", action: { /* 点击 */ })
Button("标题", role: .destructive, action: { })
Button(action: { }, label: { /* 自定义外观 */ })
Button("标题", systemImage: "plus") { } // 较新系统:标题+SF Symbol
| 参数 | 类型 | 含义 |
|---|---|---|
| 标题字符串 | String / LocalizedStringKey | 按钮上显示的文字 |
action | () -> Void | 点击时执行的闭包(必写逻辑) |
role | ButtonRole? | 语义角色,影响颜色/确认对话框位置,见下表 |
label | @ViewBuilder | 完全自定义按钮外观(图标+文字等) |
role 全部取值| 取值 | 含义 |
|---|---|
nil(不写) | 普通按钮 |
.destructive | 破坏性操作(删除等),通常呈红色 |
.cancel | 取消;在确认框里往往被放到特殊位置 |
| 修饰符 | 常用取值 | 含义 |
|---|---|---|
.buttonStyle | .automatic | 跟场景自动选样式(默认) |
.borderedProminent | 实心强调(主按钮) | |
.bordered | 描边次要按钮 | |
.borderless | 无边框 | |
.plain | 朴素,少视觉重量 | |
.controlSize | .mini / .small / .regular / .large / .extraLarge | 控件尺寸档 |
.disabled | true/false | 禁用点击并变灰 |
.tint | Color | 着色(影响 Prominent 等) |
.keyboardShortcut | 如 .defaultAction | 键盘快捷键(Mac/iPad) |
VStack(spacing: 12) {
Button("主操作") { print("go") }
.buttonStyle(.borderedProminent)
Button("次要", role: .cancel) { }
.buttonStyle(.bordered)
Button("删除", role: .destructive) { }
Button {
print("自定义")
} label: {
Label("收藏", systemImage: "star")
}
}TextField / SecureField 常用单行输入;密码用 SecureField。
TextField(_ titleKey: String, text: Binding<String>)
TextField(_ titleKey: String, text: Binding<String>, axis: Axis) // 可竖向扩展的多行(较新)
SecureField(_ titleKey: String, text: Binding<String>) // 密码,输入变圆点
| 参数 | 含义 |
|---|---|
titleKey / 占位文案 | 没输入时的提示文字(并参与无障碍) |
text | Binding<String>,与 @State 双向绑定,必用 $ |
axis | .horizontal / .vertical;竖向时可多行增高 |
prompt(部分重载) | 更明确的占位 View |
| 修饰符 | 常用取值 | 含义 |
|---|---|---|
.textFieldStyle | .automatic / .plain / .roundedBorder | 外观;圆角边框最直观 |
.keyboardType | .default / .asciiCapable / .numbersAndPunctuation / .URL / .numberPad / .phonePad / .namePhonePad / .emailAddress / .decimalPad / .twitter / .webSearch / .asciiCapableNumberPad | 弹出哪种键盘 |
.textInputAutocapitalization | .never / .words / .sentences / .characters | 自动大写策略;邮箱常用 .never |
.autocorrectionDisabled | true | 关闭自动纠正 |
.submitLabel | .done / .go / .send / .join / .route / .search / .return / .next / .continue | 键盘回车键显示文案 |
.onSubmit | 闭包 | 用户点回车时回调 |
.focused | FocusState 绑定 | 程序化聚焦/失焦 |
@State private var email = ""
@State private var pwd = ""
// …
VStack {
TextField("邮箱", text: $email)
.keyboardType(.emailAddress)
.textInputAutocapitalization(.never)
.textFieldStyle(.roundedBorder)
SecureField("密码", text: $pwd)
.textFieldStyle(.roundedBorder)
}TextEditor 常用多行文本编辑(笔记、反馈、长描述)。单行输入用 TextField。
TextEditor(text: Binding<String>)
| 参数 | 类型 | 默认 | 含义 |
|---|---|---|---|
text | Binding<String> | (必填) | 编辑内容的双向绑定,必须用 $(如 $note) |
没有 placeholder / 标题参数;占位提示要自己叠一层(见示例)。默认自带系统背景,外观主要靠修饰符。
| 修饰符 | 含义 |
|---|---|
.frame(minHeight:) | 给最小高度,否则可能塌成一行高 |
.font / .foregroundStyle | 改字号、字色 |
.scrollContentBackground(.hidden) | (较新)隐藏默认滚动背景,方便自定义底色 |
.disabled / .focused | 禁用、焦点控制 |
.overlay(RoundedRectangle…stroke) | 自己画边框(系统 TextEditor 默认不一定有清晰描边) |
@State private var note = ""
ZStack(alignment: .topLeading) {
TextEditor(text: $note)
.frame(minHeight: 120)
// 简易占位:内容为空时显示灰字
if note.isEmpty {
Text("写点什么…")
.foregroundStyle(.tertiary)
.padding(.horizontal, 5)
.padding(.vertical, 8)
.allowsHitTesting(false)
}
}
.overlay(RoundedRectangle(cornerRadius: 8).stroke(.quaternary))
Toggle 常用布尔开关。设置页高频。
Toggle(isOn: Binding<Bool>) { /* label */ }
Toggle(_ title: String, isOn: Binding<Bool>)
Toggle(isOn: Binding<Bool>, label: () -> Label)
| 参数 | 类型 | 含义 |
|---|---|---|
isOn | Binding<Bool> | 开关状态,必须用 $ 绑定 |
标题 / label | 字符串或 View | 开关左边(或环境决定位置)的说明 |
| 修饰符 | 取值 | 含义 |
|---|---|---|
.toggleStyle | .automatic | 默认 |
.switch | 经典滑动开关 | |
.button | 按钮式切换(较新) | |
.disabled | Bool | 禁用 |
.labelsHidden | — | 隐藏标签(无障碍仍可读) |
.tint | Color | 打开时的着色 |
@State private var notify = true
Toggle("推送通知", isOn: $notify)
Toggle(isOn: $notify) {
Label("通知", systemImage: "bell")
}Slider / Stepper 常用连续数值用 Slider;离散加减用 Stepper。
Slider(
value: Binding<Double>,
in: ClosedRange<Double> = 0...1,
step: Double? = nil,
onEditingChanged: (Bool) -> Void = { _ in }
)
| 参数 | 含义 |
|---|---|
value | 当前数值绑定 |
in | 可滑动范围,如 0...1、0...100 |
step | 步进;nil 连续滑,写 1 则按 1 跳动 |
onEditingChanged | 手指按下/抬起时回调,参数 true=正在拖 |
| label / minimumValueLabel / maximumValueLabel | 可选:无障碍标签、左右端点视图 |
Stepper(
"标题",
value: Binding<Int>,
in: ClosedRange<Int>,
step: Int = 1,
onEditingChanged: (Bool) -> Void = { _ in }
)
| 参数 | 含义 |
|---|---|
| 标题 / label | 显示文案(可插值显示当前值) |
value | 绑定的整数/浮点 |
in | 允许范围,如 1...99 |
step | 点一次加/减多少,默认 1 |
onEditingChanged | 编辑中状态回调 |
@State private var volume = 0.5
@State private var qty = 1
Slider(value: $volume, in: 0...1)
Stepper("数量 \(qty)", value: $qty, in: 1...20)ProgressView 常用加载中或进度条。
ProgressView() // 转圈,不确定进度
ProgressView("加载中…") // 转圈 + 文案
ProgressView(value: 0.4) // 确定进度 0...1
ProgressView(value: 30, total: 100) // 当前/总量
ProgressView(value: 0.4) { Text("下载") } currentValueLabel: { Text("40%") }
| 参数 | 含义 |
|---|---|
| (无 value) | 不确定进度(转圈) |
value | 当前进度 |
total | 总量,默认 1.0;进度 = value/total |
| label / currentValueLabel | 主说明、当前值说明 |
| 修饰符 | 取值 | 含义 |
|---|---|---|
.progressViewStyle | .automatic / .circular / .linear | 转圈或条状 |
.tint | Color | 进度条颜色 |
ProgressView("请稍候…")
ProgressView(value: 0.35)
.padding(.horizontal)Link 偶尔点一下用系统浏览器(或合适的 handler)打开 URL。隐私政策、外链、官网常用。App 内页面跳转用 NavigationLink,不要用 Link。
Link(_ title: String, destination: URL)
Link(destination: URL) { /* label View */ }
Link(_ titleKey: LocalizedStringKey, destination: URL)
| 参数 | 类型 | 含义 |
|---|---|---|
标题 / label | 字符串或 View | 链路上显示的文字或自定义视图(图标+文字等) |
destination | URL | 要点开的地址。常用 URL(string: "https://…")!(确定字符串合法时)或先 if let url = URL(string:) |
Link("隐私政策", destination: URL(string: "https://example.com/privacy")!)
Link(destination: URL(string: "https://apple.com")!) {
Label("打开 Apple", systemImage: "safari")
}
6Form、Section、Picker
Form + Section 常用设置页、资料编辑页的标准容器,系统自动分组样式。
Form { ... } —— 无初始化参数;内部用 Section 分组。系统自动给背景、分隔、边距。
Section { /* rows */ }
Section("标题") { /* rows */ }
Section { /* rows */ } header: { Text("头") } footer: { Text("尾") }
Section(isExpanded: $open) { /* iOS 17+ 可折叠 */ }
| 参数 / 闭包 | 含义 |
|---|---|
| 标题字符串 | 分组标题 |
header / footer | 自定义头尾 View |
content | 组内行(Toggle、NavigationLink 等) |
isExpanded | 绑定是否展开(可折叠 Section) |
Form {
Section("账号") {
TextField("昵称", text: $name)
Toggle("公开主页", isOn: $isPublic)
}
Section("关于") {
Text("版本 1.0.0")
}
}Picker 常用从枚举/列表里选一个值。
Picker("标题", selection: $selected) {
ForEach(options) { opt in
Text(opt.name).tag(opt) // tag 的类型必须和 selection 一致
}
}
| 参数 | 含义 |
|---|---|
| 标题 | 无障碍与部分样式下显示的标签 |
selection | Binding<Value>,当前选中项 |
content | 可选项列表;每个选项用 .tag(值) 标记 |
.pickerStyle 常用取值(写全)| 取值 | 外观 | 场景 |
|---|---|---|
.automatic | 随系统 | 默认 |
.menu | 弹出菜单 | 选项较多、省地方 |
.segmented | 分段控件 | 2~4 个互斥选项 |
.wheel | 滚轮 | 日期感、少量枚举 |
.inline | 内联展开 | 表单内 |
.navigationLink | 推进下一页再选 | 设置页层级选择 |
.palette(较新) | 调色板式 | 工具栏等 |
enum Theme: String, CaseIterable, Identifiable {
case system, light, dark
var id: String { rawValue }
}
@State private var theme: Theme = .system
Picker("外观", selection: $theme) {
ForEach(Theme.allCases) { t in
Text(t.rawValue).tag(t)
}
}
.pickerStyle(.segmented)DatePicker / ColorPicker 偶尔选日期/时间用 DatePicker;选颜色用 ColorPicker(主题色、标签色)。
DatePicker(
_ title: String,
selection: Binding<Date>,
displayedComponents: DatePickerComponents = [.date, .hourAndMinute]
)
DatePicker(
_ title: String,
selection: Binding<Date>,
in: ClosedRange<Date>, // 可选:限制可选范围
displayedComponents: DatePickerComponents
)
| 参数 | 类型 | 含义 |
|---|---|---|
| 标题 | 字符串 / View | 表单项标签 |
selection | Binding<Date> | 当前选中的日期时间 |
in | PartialRangeFrom / PartialRangeThrough / ClosedRange<Date> | 可选日期范围,如 ...Date()(不能选未来)、min...max |
displayedComponents | DatePickerComponents | 显示哪些部分,见下表 |
displayedComponents 全部常用取值| 取值 | 含义 |
|---|---|
.date | 只选年月日 |
.hourAndMinute | 只选时分 |
[.date, .hourAndMinute] | 日期 + 时间(默认观感常是二者) |
.datePickerStyle 常用取值| 取值 | 外观 | 场景 |
|---|---|---|
.automatic | 随系统 | 默认 |
.compact | 一行紧凑,点开再选 | 表单里最省地方 |
.graphical | 内联月历 | 需要一眼看见整月 |
.wheel | 滚轮 | 偏 UIKit 经典感 |
ColorPicker(_ title: String, selection: Binding<Color>, supportsOpacity: Bool = true)
ColorPicker(selection: Binding<Color>, supportsOpacity: Bool = true) { /* label */ }
| 参数 | 类型 | 默认 | 含义 |
|---|---|---|---|
| 标题 / label | 字符串或 View | — | 左侧说明 |
selection | Binding<Color> | (必填) | 当前颜色 |
supportsOpacity | Bool | true | 是否允许调透明度;只要不透明色时设 false |
@State private var birthday = Date()
@State private var accent = Color.blue
DatePicker("生日", selection: $birthday, in: ...Date(), displayedComponents: .date)
.datePickerStyle(.compact)
DatePicker("开会时间", selection: $meeting, displayedComponents: [.date, .hourAndMinute])
ColorPicker("强调色", selection: $accent, supportsOpacity: false)
7修饰符速查(按频率)
修饰符顺序有意义:先 padding 再 background,背景含内边距;反过来则背景更贴文字。
| 修饰符 | 频率 | 作用 | 常见写法 |
|---|---|---|---|
padding | 常用 | 内边距 | .padding() .padding(.horizontal, 16) |
frame | 常用 | 宽高/对齐 | .frame(maxWidth: .infinity) .frame(width:44,height:44) |
background | 常用 | 背景 | .background(.ultraThinMaterial) |
foregroundStyle | 常用 | 前景色/样式 | .foregroundStyle(.secondary) |
clipShape / cornerRadius | 常用 | 圆角裁剪 | .clipShape(RoundedRectangle(cornerRadius:12)) |
overlay | 常用 | 叠加描边/角标 | .overlay(RoundedRectangle…stroke) |
shadow | 偶尔 | 阴影 | .shadow(radius: 4, y: 2) |
opacity / hidden | 常用 | 透明/占位隐藏 | .opacity(0.5);hidden仍占位 |
disabled | 常用 | 禁用交互 | .disabled(!valid) |
ignoresSafeArea | 偶尔 | 延伸到刘海/底部 | 全屏背景图时用 |
scaledToFit/Fill | 常用 | 图片适应 | 配 resizable() |
listRowInsets 等 | 偶尔 | 列表行细节 | 见第 07 章 |
Text("卡片")
.padding()
.frame(maxWidth: .infinity, alignment: .leading)
.background(.blue.opacity(0.12))
.clipShape(RoundedRectangle(cornerRadius: 12))
.overlay(
RoundedRectangle(cornerRadius: 12)
.stroke(.blue.opacity(0.3))
)
8形状与颜色(打底常用)
RoundedRectangle / Circle / Capsule 常用当背景、头像框、按钮底、描边框。本身是形状,要用 .fill / .stroke 上色,再用 .frame 定大小。
| 形状 | 初始化 | 参数含义 |
|---|---|---|
RoundedRectangle | RoundedRectangle(cornerRadius: CGFloat, style: RoundedCornerStyle = .continuous) | cornerRadius:圆角半径(pt)。style:.continuous(iOS 更顺滑,默认观感)/ .circular(更几何) |
RoundedRectangle(cornerSize: CGSize, style:) | 宽高方向用不同圆角半径(少用) | |
Circle | Circle() | 无参数;在给定 frame 里画内切圆 |
Capsule | Capsule(style: RoundedCornerStyle = .continuous) | 两端半圆的跑道形;style 同上。无 cornerRadius(由高度自动决定) |
Rectangle / Ellipse | Rectangle() / Ellipse() | 直角矩形 / 椭圆;无初始化参数 |
| 写法 | 含义 |
|---|---|
.fill(Color / AnyShapeStyle) | 填充内部 |
.stroke(style, lineWidth:) | 只描边,中间透明 |
.strokeBorder(style, lineWidth:) | 描边画在边界内侧(不撑大布局,卡片描边常用) |
.fill(...).overlay(shape.stroke(...)) | 既填充又描边 |
HStack(spacing: 12) {
Circle().fill(.orange).frame(width: 40, height: 40)
Capsule()
.fill(.green.opacity(0.3))
.frame(width: 80, height: 28)
RoundedRectangle(cornerRadius: 8, style: .continuous)
.stroke(.blue, lineWidth: 2)
.frame(width: 60, height: 40)
}
// 卡片底:填充 + 内描边
RoundedRectangle(cornerRadius: 12)
.fill(.background)
.overlay(
RoundedRectangle(cornerRadius: 12)
.strokeBorder(.quaternary, lineWidth: 1)
)
.frame(height: 100)
9List 速览(详情见第 07 章)
常用 系统列表:滑动删除、分区、与 Navigation 绝配。
struct Row: Identifiable { let id = UUID(); let title: String }
@State private var rows = [Row(title: "A"), Row(title: "B")]
List {
ForEach(rows) { r in
Text(r.title)
}
.onDelete { rows.remove(atOffsets: $0) }
}
10上手菜谱:直接抄的布局
HStack {
VStack(alignment: .leading) {
Text("今日").font(.largeTitle.bold())
Text("3 项待办").foregroundStyle(.secondary)
}
Spacer()
Button { } label: {
Image(systemName: "plus.circle.fill").imageScale(.large)
}
}
.padding()
HStack(spacing: 12) {
Image(systemName: "bell.fill")
.frame(width: 28, height: 28)
.foregroundStyle(.white)
.background(.red)
.clipShape(RoundedRectangle(cornerRadius: 6))
Text("通知")
Spacer()
Image(systemName: "chevron.right")
.foregroundStyle(.tertiary)
}
.padding(.vertical, 4)
ContentUnavailableView(
"暂无待办",
systemImage: "checklist",
description: Text("点右上角添加第一项")
)
// iOS 17+;更早可用 VStack + Image + Text 自制
11拆视图与 Preview
常用 body 超过一屏就拆;Preview 保证改 UI 不用次次 ⌘R。
struct TaskRow: View {
let title: String
var done: Bool = false
var body: some View {
HStack {
Image(systemName: done ? "checkmark.circle.fill" : "circle")
Text(title)
Spacer()
}
}
}
#Preview("一行") {
TaskRow(title: "写需求", done: true)
.padding()
}
#Preview("列表") {
List {
TaskRow(title: "A")
TaskRow(title: "B", done: true)
}
}
let / var 收配置;要改父数据再用 Binding(第 05 章)。12API 索引(按字母,方便检索)
| 名称 | 频率 | 类别 | 跳转 |
|---|---|---|---|
Button | 常用 | 控件 | # |
ColorPicker | 偶尔 | 控件 | # |
DatePicker | 偶尔 | 控件 | # |
Divider | 常用 | 布局 | # |
Form / Section | 常用 | 容器 | # |
HStack | 常用 | 布局 | # |
Image | 常用 | 展示 | # |
Label | 常用 | 展示 | # |
LazyVGrid | 偶尔 | 布局 | # |
LazyVStack | 偶尔 | 布局 | # |
List | 常用 | 列表 | # |
Picker | 常用 | 控件 | # |
ProgressView | 常用 | 控件 | # |
ScrollView | 常用 | 布局 | # |
Slider / Stepper | 常用 | 控件 | # |
Spacer | 常用 | 布局 | # |
Text | 常用 | 展示 | # |
TextField / SecureField | 常用 | 控件 | # |
TextEditor | 常用 | 控件 | # |
Toggle | 常用 | 控件 | # |
VStack | 常用 | 布局 | # |
ZStack | 常用 | 布局 | # |
padding / frame / … | 常用 | 修饰符 | # |
13布局 FAQ
为什么按钮点不到 / 颜色不对?
Button 是否 .disabled;自定义 label 是否把点击区域做太小——可加 .contentShape(Rectangle())。HStack 里文字被压扁
.lineLimit(1) + 需要时可 minimumScaleFactor;或让次要视图 .layoutPriority(-1),主文字优先。想铺满宽度
.frame(maxWidth: .infinity, alignment: .leading) 加在子视图或背景容器上。和鸿蒙 / Compose 怎么对应?
VStack≈Column,HStack≈Row,ZStack≈Stack/Box,Spacer≈空白弹性,修饰符≈链式 modifier。细节不同,心智同构。