原创

Swift UI常用基础组件(三)

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

1 输入与控制类组件

1.1 Toggle

Toggle 用于表示 开 / 关(二值)状态,常见于设置项、权限控制、功能启停。

1.1.1 Toggle 的基本用法

@State private var isOn = true

Toggle("开启通知", isOn: $isOn)

说明:

  • isOn 必须是 Binding<Bool>
  • 文本自动左对齐,开关位于右侧

1.1.2 Toggle 不带文本标签

Toggle(isOn: $isOn) {
    Text("夜间模式")
}

适合自定义 Label 结构。

1.1.3 Toggle 的常见修饰符

Toggle("定位服务", isOn: $location)
    .tint(.green)
    .disabled(!hasPermission)

常用修饰符说明:

修饰符 作用
.tint() 修改开关高亮颜色
.disabled() 禁用交互
.labelsHidden() 隐藏文本

1.2 Picker

Picker 用于在多个选项中选择一个或多个值,是 SwiftUI 中非常重要的选择型组件。

1.2.1 Picker 的基础结构

@State private var selection = 0

Picker("性别", selection: $selection) {
    Text("男").tag(0)
    Text("女").tag(1)
}

说明:

  • tag 的类型必须与 selection 一致
  • Picker 本身不存储数据

1.2.2 PickerStyle(选择器样式)

Picker("城市", selection: $city) {
    ForEach(cities, id: \.self) { city in
        Text(city)
    }
}
.pickerStyle(.menu)

常见样式:

样式 使用场景
.menu 下拉菜单
.wheel 滚轮(生日、地区)
.segmented 少量选项切换
.navigationLink 跳转选择页

1.2.3 Picker 与 Form 的结合

Form {
    Picker("语言", selection: $language) {
        Text("中文").tag("zh")
        Text("English").tag("en")
    }
}

Form 会自动渲染为系统设置样式。

1.3 DatePicker

DatePicker 用于 日期 / 时间的选择,广泛用于生日、预约、日程等场景。

1.3.1 DatePicker 的基本用法

@State private var date = Date()

DatePicker("选择日期", selection: $date)

1.3.2 日期组件模式(Displayed Components)

DatePicker(
    "时间",
    selection: $date,
    displayedComponents: [.hourAndMinute]
)

可选值:

  • .date
  • .hourAndMinute
  • 二者组合

1.3.3 日期范围限制

DatePicker(
    "生日",
    selection: $birthday,
    in: ...Date()
)

说明:

  • 防止选择未来日期
  • 常用于生日、历史记录

1.4 Slider

Slider 用于 在连续数值范围内拖动选择,适合音量、亮度、进度等控制。

1.4.1 Slider 的基本用法

@State private var volume: Double = 0.5

Slider(value: $volume)

默认范围:0.0 ~ 1.0

1.4.2 自定义数值范围

Slider(value: $progress, in: 0...100)

1.4.3 Slider 与标签

Slider(
    value: $value,
    in: 0...10,
    step: 1
) {
    Text("等级")
} minimumValueLabel: {
    Text("0")
} maximumValueLabel: {
    Text("10")
}

1.5 Stepper

Stepper 用于 离散数值的递增 / 递减控制,常用于数量、等级、页码。

1.5.1 Stepper 的基础用法

@State private var count = 1

Stepper("数量:\(count)", value: $count)

1.5.2 设置范围与步长

Stepper(
    "页数",
    value: $page,
    in: 1...10,
    step: 1
)

1.5.3 自定义 Stepper 内容

Stepper {
    Text("等级:\(level)")
} onIncrement: {
    level += 1
} onDecrement: {
    level -= 1
}

适合需要:

  • 自定义逻辑
  • 条件限制
  • 网络校验

2 导航与页面结构

导航与页面结构组件用于组织页面层级、管理页面跳转、构建整体应用框架。 在 SwiftUI 中,导航是状态驱动的路径管理,而不是命令式 push / pop。

2.1 NavigationStack

NavigationStack 是 SwiftUI 中现代化的导航容器,用于管理页面层级结构(iOS 16+)。

2.1.1 NavigationStack 的基本用法

NavigationStack {
    Text("首页")
        .navigationTitle("Home")
}

说明:

  • 必须作为导航根容器
  • 一个页面层级只能存在一个 NavigationStack

2.1.2 NavigationStack 与 NavigationPath

@State private var path = NavigationPath()

NavigationStack(path: $path) {
    List {
        Button("进入详情页") {
            path.append(1)
        }
    }
    .navigationDestination(for: Int.self) { value in
        Text("详情页 \(value)")
    }
}

说明:

  • NavigationPath 是类型安全的路径栈
  • 支持程序化导航

2.1.3 navigationDestination 的作用

.navigationDestination(for: String.self) { value in
    Text("参数:\(value)")
}

作用:

  • 声明目标页面
  • path.append()NavigationLink(value:) 对应

NavigationLink 用于在导航栈中触发页面跳转,是最常用的导航组件。

NavigationLink("进入详情") {
    DetailView()
}

特点:

  • 适合静态结构
  • 目标页面明确
NavigationLink("查看详情", value: 10)

配合:

.navigationDestination(for: Int.self) { id in
    DetailView(id: id)
}

优点:

  • 解耦跳转与目标视图
  • 更符合数据驱动理念
List {
    NavigationLink("设置") {
        SettingView()
    }
}

系统行为:

  • 自动显示箭头
  • 整行可点击

2.3 TabView

TabView 用于构建 多 Tab 页面结构,通常作为应用的主入口框架

2.3.1 TabView 的基础结构

TabView {
    HomeView()
        .tabItem {
            Label("首页", systemImage: "house")
        }

    ProfileView()
        .tabItem {
            Label("我的", systemImage: "person")
        }
}

说明:

  • 每个子视图对应一个 Tab
  • tabItem 必须配置

2.3.2 TabView 与 selection

@State private var selectedTab = 0

TabView(selection: $selectedTab) {
    HomeView()
        .tag(0)
        .tabItem { Text("首页") }

    MessageView()
        .tag(1)
        .tabItem { Text("消息") }
}

用途:

  • 程序化切换 Tab
  • 保存当前 Tab 状态

2.3.3 TabView 与 NavigationStack 的组合

TabView {
    NavigationStack {
        HomeView()
    }
    .tabItem {
        Label("首页", systemImage: "house")
    }

    NavigationStack {
        ProfileView()
    }
    .tabItem {
        Label("我的", systemImage: "person")
    }
}

说明:

  • 每个 Tab 独立维护导航栈
  • 推荐的主结构方案

2.4 常见结构组合模式

2.4.1 典型 App 页面结构

TabView
 ├─ NavigationStack(首页)
 │   └─ 列表 → 详情
 ├─ NavigationStack(消息)
 └─ NavigationStack(我的)

2.4.2 常见错误用法

❌ 在子页面再次嵌套 NavigationStack
❌ 在非导航环境使用 NavigationLink
❌ 多个 NavigationStack 作用于同一页面层级

2.5 总结

组件 作用
NavigationStack 管理页面层级
NavigationLink 触发页面跳转
TabView 构建主框架

设计建议:

  • 单入口导航 → NavigationStack
  • 数据驱动跳转 → NavigationLink(value:)
  • 主结构 → TabView + 多 NavigationStack

3 状态与反馈组件

3.1 ProgressView

ProgressView 用于表示 任务进行中或加载状态

3.1.1 ProgressView 的基本用法(不确定进度)

ProgressView("加载中...")

适用场景:

  • 网络请求
  • 页面初始化
  • 数据计算

3.1.2 确定进度 ProgressView

@State private var progress = 0.3

ProgressView(value: progress)

3.1.3 自定义进度范围

ProgressView(value: progress, total: 100)

3.1.4 ProgressViewStyle

ProgressView("同步中")
    .progressViewStyle(.circular)

常见样式:

样式 场景
.circular 等待 / 加载
.linear 文件下载 / 上传

3.2 Alert

Alert 用于重要提示或需要用户确认的中断式反馈

3.2.1 基础 Alert 用法

@State private var showAlert = false

Button("删除") {
    showAlert = true
}
.alert("确认删除?", isPresented: $showAlert) {
    Button("确定", role: .destructive) { }
    Button("取消", role: .cancel) { }
}

特点:

  • 中断当前操作
  • 必须用户响应

3.2.2 带消息内容的 Alert

.alert(
    "操作失败",
    isPresented: $showAlert
) {
    Button("确定", role: .cancel) {}
} message: {
    Text("请检查网络连接")
}

3.2.3 Alert 的使用规范

  • 不用于频繁提示
  • 不展示长文本
  • 一个页面避免多个 Alert

3.3 ConfirmationDialog

确认对话框,ConfirmationDialog 用于 操作确认或多选决策,是 ActionSheet 的现代替代。

3.3.1 ConfirmationDialog 基本用法

@State private var showDialog = false

Button("更多操作") {
    showDialog = true
}
.confirmationDialog(
    "请选择操作",
    isPresented: $showDialog
) {
    Button("删除", role: .destructive) {}
    Button("编辑") {}
}

3.3.2 ConfirmationDialog 与 Alert 的区别

对比项 Alert ConfirmationDialog
展示位置 中央弹窗 底部弹出
操作数量
使用场景 强确认 操作选择

3.3.3 角色(role)的作用

能够根据当前系统显示对应的提示样式

Button("删除", role: .destructive) {}

系统会自动使用危险提示样式

3.4 Toast / HUD

能够自动消失的弹窗提示,SwiftUI 官方未内置 Toast / HUD,通常需要自定义或使用第三方方案。

3.4.1 Toast / HUD 的适用场景

  • 操作成功提示
  • 非阻塞式反馈
  • 临时状态提醒

3.4.2 简易 Toast 实现思路

ZStack {
    ContentView()

    if showToast {
        Text("保存成功")
            .padding()
            .background(.black.opacity(0.7))
            .foregroundColor(.white)
            .cornerRadius(8)
    }
}

关键点:

  • 覆盖在当前视图之上
  • 自动消失
  • 不阻断交互

3.4.3 HUD(加载遮罩)示例

if isLoading {
    ProgressView("加载中...")
        .frame(maxWidth: .infinity, maxHeight: .infinity)
        .background(.black.opacity(0.3))
}

常用于:

  • 防止重复点击
  • 等待关键任务完成

3.5 使用规范与设计建议

3.5.1 反馈组件选择建议

场景 推荐组件
等待加载 ProgressView
严重操作 Alert
多选操作 ConfirmationDialog
成功提示 Toast / HUD

3.5.2 设计原则

  • 不要过度提示
  • 优先非阻塞反馈
  • 风险操作必须确认

3.6 总结

状态与反馈组件是 用户体验的重要保障

  • ProgressView:让用户“有耐心”
  • Alert:让用户“慎重操作”
  • ConfirmationDialog:让用户“自主选择”
  • Toast / HUD:让用户“被温柔提醒”

4 修饰类组件(Decorators)

修饰类组件用于在不改变原有视图结构的前提下,对视图进行视觉或布局上的增强

SwiftUI 的设计哲学是:

View 不变,行为与外观通过 Modifier 叠加。

4.1 Background / Overlay

backgroundoverlay 用于在视图的 下层 / 上层 添加额外内容,是最常用的装饰类修饰符。

4.1.1 background 的基本用法

Text("Hello")
    .padding()
    .background(Color.blue)

说明:

  • 背景视图位于当前视图下方
  • 背景大小默认等于当前视图的尺寸

4.1.2 使用 background 添加复杂背景

Text("标签")
    .padding()
    .background(
        RoundedRectangle(cornerRadius: 8)
            .fill(Color.gray.opacity(0.2))
    )

适合:

  • 胶囊标签
  • 卡片背景
  • 圆角块

4.1.3 overlay 的基本用法

Text("头像")
    .padding()
    .overlay(
        Circle()
            .stroke(Color.blue, lineWidth: 2)
    )

说明:

  • 覆盖在视图最上层
  • 常用于边框、标记、遮罩

4.1.4 background 和 overlay 对比

对比项 background overlay
位置 视图下方 视图上方
常见用途 背景、填充 边框、角标
影响布局

4.1.5 多层叠加顺序

Text("多层装饰")
    .padding()
    .background(Color.yellow)
    .overlay(
        RoundedRectangle(cornerRadius: 8)
            .stroke(Color.red)
    )

修饰顺序:

  • 从上到下依次叠加
  • 顺序不同,结果可能不同

4.2 SafeAreaInset

safeAreaInset 用于在安全区域边缘插入内容,而不是简单地忽略安全区。

4.2.1 SafeAreaInset 的基本用法

ScrollView {
    Text("内容区域")
}
.safeAreaInset(edge: .bottom) {
    Text("底部操作栏")
        .frame(maxWidth: .infinity)
        .padding()
        .background(.ultraThinMaterial)
}

说明:

  • 插入内容会挤压原有布局
  • 不覆盖内容

4.2.2 与 ignoresSafeArea 的区别

对比项 safeAreaInset ignoresSafeArea
是否挤压内容
常见用途 操作栏 / 工具条 背景铺满
安全性 需谨慎

4.2.3 顶部 SafeAreaInset

.safeAreaInset(edge: .top) {
    Text("提示信息")
        .padding()
        .background(Color.orange)
}

适合:

  • 顶部公告
  • 网络状态提示

4.2.4 SafeAreaInset 的设计建议

  • 用于固定功能区
  • 不要插入过高视图
  • 内容需明确且简洁

4.3 总结

修饰方式 核心作用
background 添加下层背景
overlay 添加上层覆盖
safeAreaInset 插入安全区内容

使用原则:

  • 装饰 ≠ 布局
  • 能用修饰符解决的问题,不新增容器
  • 优先 safeAreaInset,慎用 ignoresSafeArea

5. 动画与过渡(Animation / Transition)

5.1 Animation(动画)

5.1.1 基础动画用法

@State private var isExpanded = false

Rectangle()
    .frame(width: isExpanded ? 200 : 100)
    .animation(.easeInOut, value: isExpanded)

说明:

  • value 变化 → 动画触发
  • 只对关联状态生效

5.1.2 withAnimation 显式动画

withAnimation(.spring()) {
    isExpanded.toggle()
}

适合:

  • 按钮点击
  • 手势触发

5.1.3 常见动画类型

动画 特点
.easeIn 慢入
.easeOut 慢出
.easeInOut 平滑
.linear 匀速
.spring() 弹性

5.1.4 动画时长与参数

.animation(.easeInOut(duration: 0.3), value: isExpanded)
.spring(response: 0.4, dampingFraction: 0.7)

5.2 Transition(过渡)

Transition 用于视图插入与移除时的动画效果

5.2.1 基本 Transition 用法

if isShow {
    Text("Hello")
        .transition(.opacity)
}

⚠️ 必须配合:

withAnimation {
    isShow.toggle()
}

5.2.2 常见 Transition 类型

过渡 说明
.opacity 渐隐渐现
.move(edge:) 从边缘滑入
.scale 缩放
.slide 系统滑动

5.2.3 组合 Transition

.transition(
    .move(edge: .bottom)
    .combined(with: .opacity)
)

5.2.4 asymmetric(非对称过渡)

.transition(
    .asymmetric(
        insertion: .move(edge: .bottom),
        removal: .opacity
    )
)

适合:

  • 弹窗
  • Toast / HUD

5.3 动画与过渡的区别

对比项 Animation Transition
触发方式 状态变化 视图出现/消失
作用对象 属性 View 本身
是否常驻

5.4 动画使用规范

  • 不为静态内容加动画
  • 关键操作使用短动画(≤ 0.3s)
  • 避免多动画叠加

6 手势与事件(Gesture)

手势用于捕获用户的直接交互行为,如点击、拖拽、缩放等。

6.1 TapGesture(点击)

6.1.1 基础点击手势

Text("点我")
    .onTapGesture {
        print("Tapped")
    }

6.1.2 多次点击

.onTapGesture(count: 2) {
    print("Double Tap")
}

6.2 LongPressGesture(长按)

6.2.1 基础长按

Text("长按")
    .onLongPressGesture {
        print("Long Press")
    }

6.2.2 带状态回调

.onLongPressGesture(
    minimumDuration: 1,
    pressing: { isPressing in
        print(isPressing)
    }
) {
    print("完成")
}

6.3 DragGesture(拖拽)

6.3.1 基本拖拽

@State private var offset = CGSize.zero

Rectangle()
    .offset(offset)
    .gesture(
        DragGesture()
            .onChanged { value in
                offset = value.translation
            }
            .onEnded { _ in
                offset = .zero
            }
    )

6.3.2 与动画结合

.onEnded { _ in
    withAnimation {
        offset = .zero
    }
}

6.4 MagnificationGesture(缩放)

@State private var scale: CGFloat = 1

Image("photo")
    .scaleEffect(scale)
    .gesture(
        MagnificationGesture()
            .onChanged { scale = $0 }
    )

6.5 RotationGesture(旋转)

@State private var angle: Angle = .zero

Rectangle()
    .rotationEffect(angle)
    .gesture(
        RotationGesture()
            .onChanged { angle = $0 }
    )

6.6 手势组合

6.6.1 同时识别(Simultaneous)

.simultaneousGesture(
    TapGesture()
        .onEnded { print("Tap") }
)

6.6.2 优先级(High Priority)

.highPriorityGesture(
    DragGesture()
)

6.6.3 排他识别(Exclusive)

.exclusiveGesture(
    TapGesture(),
    LongPressGesture()
)

6.7 手势使用规范

  • 避免与系统手势冲突
  • 列表中慎用复杂手势
  • 手势应有明确反馈(动画)

6.8 本章小结

类型 作用
Tap 点击
LongPress 长按
Drag 拖拽
Magnification 缩放
Rotation 旋转

核心原则:

手势 ≠ 功能
手势是触发方式,不是业务本身

7 状态管理

类型 用途
@State 本地状态
@Binding 双向绑定
@ObservedObject 外部对象
@StateObject 生命周期绑定
@Environment 环境变量

8 最常用组件速查表

类型 组件
布局 HStack / VStack / ZStack
列表 List / ScrollView
表单 Form / Section
控制 Toggle / Picker / Slider
导航 NavigationStack / TabView
反馈 ProgressView / Alert
正文到此结束
本文目录