原创

Swift UI常用基础组件(二)

温馨提示:
本文最后更新于 2026年01月07日,已超过 70 天没有更新。若文章内的图片失效(无法正常加载),请留言反馈或直接联系我

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()
}

👉 边框请用 .overlayRoundedRectangle

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 依赖 Identifiableid 参数进行差异化更新。

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("地区")
    }
}
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 交互

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)
    }
}

作用:

  • 提升可读性
  • 符合系统设置页结构
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。

正文到此结束
本文目录