Swift UI常用基础组件(二)
1 封装组件
1.1 基础使用
需要继承View组件,然后定义对应的参数
struct XxxView: View {
var title: String
var imageName: String
var degree: Int
var unit: String = "摄氏度" // 可选的单位,默认空
var body: some View {
VStack(spacing: 8) { // 调整子视图间距
Text(title)
.font(.subheadline)
.foregroundColor(.gray)
Image(systemName: imageName)
.font(.title)
.foregroundColor(.blue)
Text("\(degree)\(unit)") // 拼接单位
.font(.headline)
}
.padding() // 增加内边距
}
}
1.2 使用
// 使用时可传单位
XxxView(title: "温度", imageName: "thermometer", degree: 25, unit: "°C")
2 Spacer
2.1 简介
Spacer 是一个 “可伸缩的空白视图”,用于:
- 在 Stack(HStack / VStack)中
- 占据多余空间
- 从而 推开其他视图
HStack {
Text("左")
Spacer()
Text("右")
}
效果:
👉 左右两端对齐,中间自动填充
2.2 Spacer 的本质
Spacer 并不是固定尺寸的间距,而是“参与布局分配的弹性视图”
布局规则核心:
- Stack 会先计算 非 Spacer 子视图的最小尺寸
- 剩余空间由 Spacer 平分
- Spacer 的大小是 动态的
2.3 Spacer 的基本用法
2.3.1在 HStack 中(最常见)
HStack {
Image(systemName: "arrow.left")
Spacer()
Text("标题")
Spacer()
Image(systemName: "ellipsis")
}
📌 效果:
- 标题居中
- 左右按钮贴边
2.3.2 在 VStack 中
VStack {
Spacer()
Text("居中内容")
Spacer()
}
📌 效果:
- 内容垂直居中
2.3.3 在 ZStack 中(⚠️注意)
ZStack {
Text("背景")
Spacer() // ❌ 无效
}
❗
Spacer只在 Stack(H/V)中生效
2.4 Spacer 的参数
Spacer(minLength:)
Spacer(minLength: 20)
| 参数 | 含义 |
|---|---|
minLength |
Spacer 最小尺寸 |
📌 特点:
- 只保证 最小
- 不保证最终大小
示例:限制最小间距
HStack {
Text("A")
Spacer(minLength: 8)
Text("B")
}
2.5 多个 Spacer 的分配规则
2.5.1 示例:
HStack {
Text("左")
Spacer()
Spacer()
Text("右")
}
📌 结果:
- 中间空间被 两个 Spacer 平分
- Spacer 权重相同
2.5.2 模拟“权重”
SwiftUI 没有 weight,但可通过多个 Spacer 实现:
Spacer()
Spacer()
Spacer()
≈ 权重 3
2.6 Spacer vs padding / spacing
| 对比 | Spacer | padding / spacing |
|---|---|---|
| 是否动态 | ✅ | ❌ |
| 占满剩余空间 | ✅ | ❌ |
| 用于对齐 | ✅ | ❌ |
| 固定间距 | ❌ | ✅ |
正确选择:
- 对齐 / 推开内容 → Spacer
- 固定间距 / 内边距 → padding / spacing
2.7 Spacer 与 frame(maxWidth / maxHeight)
等价写法(部分场景)
Text("左")
.frame(maxWidth: .infinity, alignment: .leading)
vs
HStack {
Text("左")
Spacer()
}
📌 区别:
Spacer更语义化frame更灵活
2.8 其他
2.8.1 Spacer 常见误区
❌ 1. Spacer 不生效
Spacer()
原因:
- 不在
HStack / VStack - 父视图没有多余空间
❌ 2. Spacer 当成间距使用
Text("A")
Spacer()
Text("B")
👉 结果不确定,不是固定距离
❌ 3. Spacer 在 ScrollView 中异常
ScrollView {
VStack {
Spacer() // 可能无效
}
}
原因:
- ScrollView 默认 内容高度自适应
解决:
.frame(maxHeight: .infinity)
2.8.2 典型实战场景
1️⃣ 导航栏布局
HStack {
Button("返回") {}
Spacer()
Text("标题")
Spacer()
Button("更多") {}
}
2️⃣ 底部按钮顶到下方
VStack {
ContentView()
Spacer()
Button("提交") {}
}
3️⃣ 卡片内容对齐
HStack {
VStack(alignment: .leading) {
Text("标题")
Text("描述")
}
Spacer()
Image(systemName: "chevron.right")
}
3 Divider
3.1 简介
Divider 是 SwiftUI 中的 分割线视图,用于:
- 分隔内容区域
- 提升层级与可读性
- 常用于 List / Form / Stack 中
Divider()
本质:一个 自适应方向的细线视图
3.2 Divider 的方向规则
Divider 的方向由“父容器的布局方向”决定
| 父容器 | Divider 表现 |
|---|---|
VStack |
横向分割线 |
HStack |
纵向分割线 |
List / Form |
系统样式分割线 |
ZStack |
❌ 无意义 |
3.2.1示例 1:VStack 中(横线)
VStack {
Text("上")
Divider()
Text("下")
}
3.2.2示例 2:HStack 中(竖线)
HStack {
Text("左")
Divider()
Text("右")
}
3.3 Divider 的尺寸与布局行为
默认行为:
- 厚度:1 px(系统最小)
- 颜色:secondary / separator
- 长度:占满可用空间
Divider()
3.3.1控制长度(⚠️常见需求)
Divider()
.frame(width: 100)
或
Divider()
.padding(.horizontal, 16)
3.3.2 控制高度(竖线)
Divider()
.frame(height: 20)
3.4 Divider 的样式控制
⚠️ Divider 没有直接的颜色 / 粗细 API
3.4.1 改颜色(推荐方式)
Divider()
.background(Color.gray)
Divider 本身透明,背景决定颜色
3.4.2 改粗细(常用技巧)
Divider()
.frame(height: 2)
.background(Color.blue)
3.4.3 自定义分割线(更可控)
Rectangle()
.fill(Color.gray)
.frame(height: 1)
📌 当你需要:
- 虚线
- 动画
- 渐变
👉 用 Rectangle 替代 Divider
3.5 Divider 与 padding / Spacer 的关系
3.5.1 常见组合
VStack {
Text("标题")
Divider()
.padding(.vertical, 8)
Text("内容")
}
3.5.2 Divider vs Spacer(⚠️核心区别)
| 对比 | Divider | Spacer |
|---|---|---|
| 是否可见 | ✅ | ❌ |
| 是否分隔内容 | ✅ | ❌ |
| 是否占剩余空间 | ❌ | ✅ |
| 语义 | 分割 | 推开 |
3.6 Divider 在 List / Form 中
3.6.1 List 中
List {
Text("A")
Divider() // ❌ 通常不需要
Text("B")
}
List 自带分割线,除非你禁用了系统样式
3.6.2 关闭系统分割线(iOS 15+)
.listRowSeparator(.hidden)
自定义 Divider:
Divider()
.padding(.leading)
3.6.3 Form 中
Form {
Section {
Text("账号")
Divider()
Text("密码")
}
}
3.7 其他
3.7.1 常见误区
❌ 1. Divider 不显示
原因:
- 父视图空间为 0
- 在 ZStack 中
- 背景色与父视图一致
❌ 2. Divider 不能变颜色
👉 实际是要用 .background()
❌ 3. Divider 当边框用
VStack {
Divider()
}
👉 边框请用 .overlay 或 RoundedRectangle
3.7.2 典型实战场景
1. 列表项分割
VStack(spacing: 0) {
RowView()
Divider()
}
2. 设置页分区
VStack {
SettingItem()
Divider()
SettingItem()
}
3. 横向按钮分隔
HStack {
Button("取消") {}
Divider()
Button("确定") {}
}
.frame(height: 44)
4 GeometryReader
4.1 简介
GeometryReader 是一个 读取父视图几何信息的容器视图,可以获取:
- 可用宽高(size)
- 坐标信息(frame)
- 安全区域(safeAreaInsets)
GeometryReader { geometry in
Text("宽度:\(geometry.size.width)")
}
核心:它不只是“读尺寸”,而是“参与布局”
4.2 GeometryReader 的本质
GeometryReader 会尽可能占满父视图提供的所有空间
这点是 90% 布局 Bug 的根源。
VStack {
GeometryReader { geo in
Text("内容")
}
Text("下面内容")
}
📌 结果:
- GeometryReader 吃掉剩余空间
- 下面 Text 被挤到屏幕底部
4.3 GeometryProxy 能读到什么
geometry
的类型是GeometryProxy
4.3.1 size
geometry.size.width
geometry.size.height
👉 当前视图 可用空间大小
4.3.2 frame(in:)
geometry.frame(in: .global)
geometry.frame(in: .local)
geometry.frame(in: .named("scroll"))
坐标空间说明
| 空间 | 含义 |
|---|---|
.local |
当前容器坐标 |
.global |
屏幕坐标 |
.named |
自定义命名空间 |
4.3.3 safeAreaInsets(iOS 15+)
geometry.safeAreaInsets.top
4.4 GeometryReader 的正确使用方式
4.4.1 推荐用法 1:包在 background / overlay
Text("内容")
.background(
GeometryReader { geo in
Color.clear
.onAppear {
print(geo.size)
}
}
)
👉 不会影响布局
4.4.2 推荐用法 2:明确限制尺寸
GeometryReader { geo in
Text("50% 宽")
.frame(width: geo.size.width * 0.5)
}
.frame(height: 100)
4.4.3 错误用法:当容器用
GeometryReader {
VStack { ... }
}
👉 除非你真的要吃满空间
4.5 典型实战场景
4.5.1 自适应宽高比例(最常见)
GeometryReader { geo in
Image("banner")
.resizable()
.frame(width: geo.size.width,
height: geo.size.width * 9 / 16)
}
.frame(height: UIScreen.main.bounds.width * 9 / 16)
4.5.2 吸顶 / 滚动监听(配合 ScrollView)
GeometryReader { geo in
let offset = geo.frame(in: .global).minY
Text("Header")
.opacity(offset < 0 ? 0 : 1)
}
.frame(height: 60)
4.5.3居中对齐(⚠️非推荐)
GeometryReader { geo in
Text("居中")
.position(x: geo.size.width / 2,
y: geo.size.height / 2)
}
👉 不推荐,优先用 Spacer
4.6 其他
4.6.1 常见坑
❌ 1. 影响父布局
GeometryReader 默认 max size
❌ 2. 在 List / LazyStack 中异常
- 高度变成无穷大
- 内容错乱
👉 解决:明确 frame
❌ 3. 用 GeometryReader 做对齐
👉 这是 Spacer / alignment 的工作
❌ 4. 频繁触发布局计算
- 滚动中使用 frame(in: .global)
- 性能下降
4.6.2 替代方案
| 需求 | 更好的方式 |
|---|---|
| 居中 | Spacer / alignment |
| 自适应字体 | Dynamic Type |
| 获取尺寸 | background + GeometryReader |
| 安全区 | safeAreaInset |
4.6.3 完整推荐模板
struct SizeReader: View {
var onChange: (CGSize) -> Void
var body: some View {
GeometryReader { geo in
Color.clear
.onAppear {
onChange(geo.size)
}
.onChange(of: geo.size) { newSize in
onChange(newSize)
}
}
}
}
5 List
5.1 简介
List 是 SwiftUI 中用于展示垂直滚动数据集合的高性能容器组件,主要用于:
- 数据列表展示
- 设置页(Settings)
- 表单式页面
- 大数据量滚动视图
List 的底层在 iOS 上基于 UITableView / UICollectionView,具备视图复用、懒加载、自动滚动优化等能力。
5.1.1 List 的基本特性
- 自动支持垂直滚动
- 默认自带分割线
- 支持大规模数据(数百 / 数千条)
- 与数据源强绑定(数据驱动 UI)
List {
Text("Item 1")
Text("Item 2")
}
5.2 List 的创建方式
5.2.1 静态 List
用于展示固定内容。
List {
Text("账户")
Text("隐私")
Text("关于")
}
特点:
- 结构清晰
- 常用于设置页
5.2.2 动态 List(ForEach)
用于展示动态数据源,像从接口请求的数据要展示等
struct Item: Identifiable {
let id = UUID()
let title: String
}
List(items) { item in
Text(item.title)
}
List 依赖 Identifiable 或 id 参数进行差异化更新。
5.2.3 使用 id 参数
List(items, id: \.self) { item in
Text(item)
}
适用于:
- String / Int 等基础类型
5.3 List 的分组与区块(Section)
5.3.1 Section 的基本使用
List {
Section(header: Text("常规")) {
Text("语言")
Text("地区")
}
}
5.3.2 Header 与 Footer
Section(
header: Text("账户"),
footer: Text("用于登录和安全验证")
) {
Text("手机号")
Text("邮箱")
}
5.3.3 Section 的样式行为
- Header 默认吸顶
- Footer 不吸顶
- 可配合
.listStyle改变整体风格
5.4 List 行(Row)的构成
5.4.1 自定义 Row 内容
List(items) { item in
HStack {
Image(systemName: "star")
Text(item.title)
Spacer()
Image(systemName: "chevron.right")
}
}
5.4.2 行高控制
.listRowInsets(EdgeInsets())
或:
.frame(height: 60)
5.4.3 行背景
.listRowBackground(Color.gray.opacity(0.1))
5.5 List 交互
5.5.1 行点击(Button / NavigationLink)
List(items) { item in
NavigationLink {
DetailView(item: item)
} label: {
Text(item.title)
}
}
5.5.2 滑动删除(Delete)
.onDelete { indexSet in
items.remove(atOffsets: indexSet)
}
5.5.3 行移动(Move)
.onMove { source, destination in
items.move(fromOffsets: source, toOffset: destination)
}
5.6 List 的样式
5.6.1 listStyle
.listStyle(.plain)
常见样式:
| 样式 | 说明 |
|---|---|
.plain |
普通列表 |
.grouped |
分组 |
.insetGrouped |
设置页 |
.sidebar |
侧边栏 |
5.6.2 分割线控制(iOS 15+)
.listRowSeparator(.hidden)
.listRowSeparatorTint(.gray)
5.7 List 的性能与懒加载
5.7.1 List 的懒加载机制
- 只创建可见行
- 自动回收离屏视图
- 比
ScrollView + VStack更高效
5.7.2 List 和 LazyVStack
| 对比 | List | LazyVStack |
|---|---|---|
| 复用 | ✅ | ❌ |
| 系统交互 | ✅ | ❌ |
| 自定义能力 | 中 | 高 |
5.8 List 的常见问题与规范
5.8.1 List 中避免使用 GeometryReader
- 容易造成高度异常
- 影响滚动性能
5.8.2 避免嵌套 List
- 滚动冲突
- 不可预期行为
5.8.3 行内容避免复杂层级
- 保持 Row View 轻量
- 提升滚动流畅度
5.9 标准列表页面示例
5.9.1 示例代码
NavigationStack {
List {
Section("功能") {
NavigationLink("账户") { AccountView() }
NavigationLink("隐私") { PrivacyView() }
}
}
.listStyle(.insetGrouped)
.navigationTitle("设置")
}
5.9.2 总结
List 是 SwiftUI 中最适合展示结构化数据的组件,其优势在于:
- 高性能
- 高一致性
- 原生系统交互
当需要“像系统设置一样的列表”,优先选择 List,而不是 ScrollView。
6 Form
6.1 简介
Form 是 SwiftUI 中用于构建表单式界面的高级容器组件,主要用于:
- 设置页(Settings)
- 用户信息填写
- 表单提交(登录 / 注册 / 编辑资料)
- 偏“系统风格”的输入页面
Form {
TextField("用户名", text: $username)
Toggle("开启通知", isOn: $enabled)
}
Form 在 iOS 上底层基于 UITableView,因此具备系统级交互一致性。
6.1.1 Form 的核心特性
- 自动纵向滚动
- 行高、间距遵循系统规范
- 与输入组件(TextField / Toggle / Picker)高度契合
- 默认支持键盘交互与焦点管理
6.2 Form 的创建方式
6.2.1 基本 Form 结构
Form {
Text("基础信息")
TextField("姓名", text: $name)
}
特点:
- 子视图自动作为“行(Row)”展示
- 不需要额外的 Stack 布局
6.2.2 Form 数据绑定
@State private var email = ""
Form {
TextField("邮箱", text: $email)
}
Form 本身不管理数据,完全依赖 SwiftUI 的状态系统。
6.3 Form 与 Section(分组)
6.3.1 使用 Section 进行逻辑分区
Form {
Section("账号") {
TextField("手机号", text: $phone)
SecureField("密码", text: $password)
}
}
作用:
- 提升可读性
- 符合系统设置页结构
6.3.2 Header 与 Footer
Section(
header: Text("隐私"),
footer: Text("这些信息仅用于账户安全")
) {
Toggle("允许定位", isOn: $location)
}
说明:
- Header 默认吸顶
- Footer 用于补充说明
6.4 Form 中的常用组件
6.4.1 输入组件
TextField("昵称", text: $nickname)
SecureField("密码", text: $password)
6.4.2 开关与选择组件
Toggle("接收通知", isOn: $notify)
Picker("性别", selection: $gender) {
Text("男").tag(0)
Text("女").tag(1)
}
6.4.3 日期与数值组件
DatePicker("生日", selection: $birthday)
Stepper("数量 \(count)", value: $count)
6.5 Form 交互
6.5.1 行点击与 Button
Form {
Button("退出登录") {
logout()
}
}
Button 会自动表现为:
- 整行可点击
- 系统高亮反馈
6.5.2 禁用状态
Form {
TextField("邮箱", text: $email)
}
.disabled(isLoading)
说明:
- 整个 Form 或单行都可禁用
- 系统自动降低可用性表现
6.6 Form 的样式与行为特性
6.6.1 Form 的默认样式
- 行内边距由系统控制
- 不建议强行自定义高度
- 分割线自动管理
6.6.2 Form 与 listStyle 的关系
Form {
Section {
Text("示例")
}
}
.listStyle(.insetGrouped)
常见效果:
.insetGrouped:设置页风格.grouped:传统分组列表
6.7 Form 与 List 的对比
6.7.1 Form vs List
| 对比项 | Form | List |
|---|---|---|
| 主要用途 | 表单 / 设置 | 数据展示 |
| 输入组件 | ⭐⭐⭐⭐⭐ | ⭐⭐ |
| 自定义能力 | 较低 | 中 |
| 系统一致性 | 极高 | 高 |
6.7.2 使用建议
- 偏输入 → Form
- 偏展示 → List
- 设置页:Form 是首选
6.8 Form 常见问题
6.8.1 避免复杂自定义布局
Form {
VStack { ... } // ❌ 不推荐
}
原因:
- 破坏系统行布局
- 容易出现高度异常
6.8.2 不要嵌套 ScrollView
Form {
ScrollView { } // ❌
}
Form 自带滚动能力,嵌套会导致冲突。
6.8.3 避免过度样式化
- 不建议自定义背景色
- 不建议手动画分割线
6.9 综合示例
6.9.1 示例代码
NavigationStack {
Form {
Section("账号") {
TextField("用户名", text: $username)
SecureField("密码", text: $password)
}
Section("偏好") {
Toggle("深色模式", isOn: $darkMode)
}
Section {
Button("退出登录", role: .destructive) {
logout()
}
}
}
.navigationTitle("设置")
}
6.9.2 总结
Form 是 SwiftUI 中最贴近系统设置体验的组件:
- 强一致性
- 低定制成本
- 高可维护性
当你的页面“看起来像系统设置”,那它就应该是 Form。
- 本文标签: swift ui
- 本文链接: https://www.tianyajuanke.top/article/95
- 版权声明: 本文由吴沛芙原创发布,转载请遵循《署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0)》许可协议授权