🚀 SwiftUI 零基础实战:10 分钟学会做一个 App 引导页
摘要: 本文手把手带你用 SwiftUI 实现一个 App 首次启动时的引导页,包含图片轮播、分页滑动、按钮交互等功能。所有概念用大白话解释,即使你一行 SwiftUI 代码都没写过,也能跟着看懂,并跑通自己的第一个小程序。
目录
- 一、先看看我们要做什么
- 二、环境准备:你需要装什么
- [三、第一步:导入 SwiftUI 工具箱](#三、第一步:导入 SwiftUI 工具箱)
- [四、第二步:让 Color 支持十六进制颜色码](#四、第二步:让 Color 支持十六进制颜色码)
- 五、第三步:搭建引导页界面
- [5.1 准备工作:数据与状态](#5.1 准备工作:数据与状态)
- [5.2 绘制界面:从外到内层层搭建](#5.2 绘制界面:从外到内层层搭建)
- [5.3 背景色:铺满屏幕的底色](#5.3 背景色:铺满屏幕的底色)
- [5.4 分页滑动:TabView 实现图片轮播](#5.4 分页滑动:TabView 实现图片轮播)
- [5.5 页面内容:图片 + 文案 + 按钮](#5.5 页面内容:图片 + 文案 + 按钮)
- [5.6 "立即体验"按钮:只在最后一页出现](#5.6 "立即体验"按钮:只在最后一页出现)
- [5.7 小圆点:页面指示器](#5.7 小圆点:页面指示器)
- 六、预览运行:看看效果
- 七、完整代码
- 八、总结:你刚刚学会了什么
一、先看看我们要做什么
我们要实现一个 App 首次启动时出现的引导页,效果如下:
- 📱 进入 App 后,看到第一张欢迎图片和介绍文字
- 👉 手指向左滑动,切换到下一张介绍图
- 🎨 每张图对应不同的背景颜色
- 📍 底部有小圆点指示器,告诉用户当前在第几页
- 🔘 滑到最后一页时,出现"立即体验"按钮,点击进入 App 主界面
这几个功能几乎是所有 App 引导页的标配。接下来,我们一步步把它做出来。

二、环境准备:你需要装什么
如果你用的是 Mac 电脑 ,去 App Store 下载 Xcode (苹果官方开发工具,免费),然后创建一个新的 iOS App 项目。创建项目时,记得界面(Interface)选 SwiftUI。
提示:本教程基于 Xcode 15 + SwiftUI 5,更低版本也能运行。
三、第一步:导入 SwiftUI 工具箱
swift
import SwiftUI
这是整段代码的第一行,也是最简单的一行。它的作用是:
把苹果提供的界面控件(按钮、文字、图片、布局等)全部导入进来,我们后面才能使用它们。
可以把它理解为:你要做菜,先打开冰箱把食材拿出来。import 就是"拿食材"这一步。
四、第二步:让 Color 支持十六进制颜色码
问题出在哪?
SwiftUI 自带的 Color 类型,支持直接写 .red、.blue 这样的颜色,但在实际开发中,设计师给的通常是这种格式:
#ccecff (浅蓝色)
#56565b (深灰色)
#a7e8c3 (浅绿色)
这种叫十六进制颜色码(hex color),每两位代表红、绿、蓝的强度。SwiftUI 默认不支持这种写法,所以我们需要自己扩展一下。
代码实现
swift
extension Color {
init(hex: String) {
// 1. 把输入的颜色码处理干净(比如去掉可能多出来的 # 号)
let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
// 2. 把十六进制字符串变成数字
var int: UInt64 = 0
Scanner(string: hex).scanHexInt64(&int)
let a, r, g, b: UInt64
// 3. 根据不同的长度,用不同的方式提取红、绿、蓝值
switch hex.count {
case 3: // 缩写格式,如 #cce → #ccccce
(a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
case 6: // 标准格式,如 #ccecff
(a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
case 8: // 带透明度的格式,如 #ffccecff
(a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
default: // 格式不对,返回白色
(a, r, g, b) = (1, 1, 1, 0)
}
// 4. 用算好的值创建真正的颜色对象
self.init(
.sRGB,
red: Double(r) / 255,
green: Double(g) / 255,
blue: Double(b) / 255,
opacity: Double(a) / 255
)
}
}
逐行拆解
| 代码片段 | 大白话解释 |
|---|---|
extension Color { ... } |
"扩展 Color 这个类型,给它加一个新功能" |
init(hex: String) |
"用一串 #ccecff 这样的文字来创建一个颜色" |
trimmingCharacters(...) |
"把输入里非字母数字的字符去掉,防止多带了 # 号" |
Scanner(...) |
Swift 里把字符串转成数字的工具,比如 "ccecff" → 一个十六进制整数 |
>> & |
位运算,把一个大整数拆成红、绿、蓝、透明度四个小数值 |
Double(r) / 255 |
颜色值范围是 0~1,所以把 0~255 除以 255 转换过去 |
为什么需要这段代码?
有了它,后面我们就可以直接写:
swift
Color(hex: "#ccecff") // 浅蓝色
Color(hex: "#a7e8c3") // 浅绿色
而不是只能用系统预设的 .red、.blue 等少量颜色。这在实际项目中非常重要。
💡 通俗比喻:
extension就像你给手机装了一个第三方输入法 ------ 手机本来只能打拼音,装了扩展包后也能打五笔、手写了。这里就是给Color装了一个"用#ccecff格式创建颜色"的扩展。
五、第三步:搭建引导页界面
5.1 准备工作:数据与状态
swift
struct ContentView: View {
// 📷 准备好 4 张图片的名字
let images = ["ic_guide_1", "ic_guide_2", "ic_guide_3", "ic_guide_4"]
// 📝 每张图片对应的介绍文字
let descriptions = [
"欢迎使用 Guide 引导页",
"这是基于 SwiftUI 的引导页",
"你可以自定义引导页的样式",
"开始你的 SwiftUI 之旅"
]
// 🎨 每页的背景颜色
let backgroundColors = [
Color(hex: "#ccecff"), // 第 1 页:浅蓝
Color(hex: "#56565b"), // 第 2 页:深灰
Color(hex: "#cdebe8"), // 第 3 页:浅青
Color(hex: "#a7e8c3") // 第 4 页:浅绿
]
// 🔢 记录用户当前在第几页,初始为第 0 页
@State private var currentPage = 0
关键概念解释
let 和 var
let:这个值不会变(常量),比如引导页就 4 张图,不会多也不会少var:这个值会变(变量),比如用户滑到哪一页是不断变化的
@State 是做什么的?
这是 SwiftUI 里非常重要的概念。@State 的意思是:
这个值发生变化时,界面自动跟着刷新。
举个例子:当用户从第 1 张图滑到第 2 张图,currentPage 从 0 变成 1,SwiftUI 检测到这个变化,自动把:
- 文字换成第 2 张的描述
- 背景色换成第 2 张的颜色
- 小圆点跳到第 2 个位置
这一切你都不用手动操作,SwiftUI 帮你自动完成了。 这就是 SwiftUI "声明式 UI"的核心理念 ------ 你只管告诉它"数据长这样",剩下的显示工作它自动搞定。
5.2 绘制界面:从外到内层层搭建
swift
var body: some View {
ZStack { // 👈 第三层:层叠布局(背景在下,内容在上)
// 背景颜色
backgroundColors[currentPage]
...
TabView(...) { // 👈 第二层:分页滑动容器
ForEach(...) { // 👈 第一层:循环生成 4 个页面
VStack { // 👈 每个页面内部:垂直排列
// 图片
// 文字
// 按钮(仅最后一页显示)
}
}
}
}
}
看看这个嵌套结构,像不像俄罗斯套娃?其实所有 App 界面都是这样一层层嵌套出来的。我们来分别拆解每一层。
5.3 背景色:铺满屏幕的底色
swift
backgroundColors[currentPage]
.frame(maxWidth: .infinity, maxHeight: .infinity)
.ignoresSafeArea()
| 代码 | 作用 |
|---|---|
backgroundColors[currentPage] |
取当前页码对应的颜色,比如第 1 页取浅蓝 |
.frame(maxWidth: .infinity, maxHeight: .infinity) |
让这个颜色铺满整个屏幕宽高 |
.ignoresSafeArea() |
忽略安全区域,覆盖到刘海屏 / 灵动岛的顶部 |
什么是安全区域? iPhone 屏幕顶部有刘海或灵动岛,系统默认会留白避开这块区域。对引导页来说,我们希望背景颜色铺满整个屏幕(包括刘海区域),所以要加 .ignoresSafeArea()。
5.4 分页滑动:TabView 实现图片轮播
swift
TabView(selection: $currentPage) {
ForEach(images.indices, id: \.self) { index in
// 每一页的内容
...
.tag(index) // 给每一页打个标签,标记它是第几页
}
}
.tabViewStyle(.page) // 分页式滚动效果
.indexViewStyle(.page(backgroundDisplayMode: .always)) // 显示小圆点
为什么用 TabView 而不是自己写滑动代码?
SwiftUI 的 TabView 加 .tabViewStyle(.page) 可以直接实现 iOS 原生的分页滑动效果,自带惯性滚动和贴合吸附,不需要自己处理手势,开箱即用。
关键参数讲解
selection: $currentPage
$符号表示"双向绑定":用户滑动页面时,currentPage自动更新;currentPage变了,页面也自动切过去- 第 0 页 →
currentPage = 0,第 1 页 →currentPage = 1
ForEach(images.indices, id: \.self)
images.indices:图片数组的下标范围,即0, 1, 2, 3id: \.self:给每个元素一个唯一标识,SwiftUI 用它来判断"这个元素和上一个是不是同一个",以便高效刷新
可以理解为:
ForEach就像一个复读机,把"第 0 页的内容"、"第 1 页的内容"、"第 2 页的内容"、"第 3 页的内容"依次生成一遍。
5.5 页面内容:图片 + 文案 + 按钮
每一页的内部结构用 VStack(垂直排列)来组织:
swift
VStack {
Spacer() // 弹性空白:把内容往中间推
Image(images[index]) // 📷 显示图片
.resizable() // 允许缩放
.frame(width: 300, height: 300) // 固定 300×300 大小
Text(descriptions[index]) // 📝 显示介绍文字
.font(.title) // 用标题字号
.foregroundStyle(.tint) // 用系统强调色
// 按钮区域(只在最后一页显示)
if images[currentPage] == images.last {
Button(action: {}) {
Text("立即体验")
.font(.system(size: 17))
.padding()
.frame(width: 180)
.background(.white)
.cornerRadius(30)
}
.padding(.vertical, 30)
}
Spacer() // 弹性空白:把内容往中间推
}
.tag(index)
逐行解释
Spacer()
弹簧。上下各一个
Spacer(),中间的内容就被挤到屏幕正中间了。
Image(images[index]).resizable().frame(width: 300, height: 300)
显示图片,
resizable()允许它被拉伸缩放,然后固定到 300×300 的尺寸。
.font(.title)
系统预置的标题字号,比正文大,比较醒目。
.foregroundStyle(.tint)
文字用系统强调色(通常是蓝色),保持和系统一致的设计风格。
5.6 "立即体验"按钮:只在最后一页出现
swift
if images[currentPage] == images.last {
Button(action: {}) {
Text("立即体验")
.font(.system(size: 17))
.padding()
.frame(width: 180)
.background(.white)
.cornerRadius(30)
}
.padding(.vertical, 30)
}
这是引导页的经典设计:前几页只展示介绍,最后一页才出现操作按钮。
条件判断:if images[currentPage] == images.last
images.last:取数组最后一个元素,即"ic_guide_4"images[currentPage]:当前页的图片名==:判断是否相等
所以这句话的意思是:只有当用户滑到第 4 张图时,才显示按钮。
按钮样式拆解
| 代码 | 作用 |
|---|---|
Button(action: {}) |
创建一个按钮,{} 里是点击后要做的事(目前为空) |
.font(.system(size: 17)) |
按钮文字大小 17 |
.padding() |
文字四周留白,避免太挤 |
.frame(width: 180) |
按钮宽度固定为 180 |
.background(.white) |
按钮底色为白色 |
.cornerRadius(30) |
圆角半径 30,让按钮变圆润 |
.padding(.vertical, 30) |
按钮上下各留 30 的间距 |
💡 第 73 行代码有一个小问题:
images[currentPage]用在了if里,其实直接用index == images.count - 1或currentPage == images.count - 1会更直接。这正好说明一件事 ------ 代码不是一次就写对的,能跑就行,后面再优化。
5.7 小圆点:页面指示器
swift
.tabViewStyle(.page)
.indexViewStyle(.page(backgroundDisplayMode: .always))
这两行放在 TabView 的结束大括号 } 后面:
.tabViewStyle(.page):告诉 TabView 用"分页式"的滑动风格(像翻书一样,而不是底部标签栏).indexViewStyle(.page(backgroundDisplayMode: .always)):显示底部那排小圆点,backgroundDisplayMode: .always让圆点的背景始终可见(即使在深色页面上也看得清)
六、预览运行:看看效果
写完代码后,在 Xcode 右侧就能看到实时预览:
swift
#Preview {
ContentView()
}
#Preview 是 SwiftUI 的预览宏,告诉 Xcode:"帮我把 ContentView 渲染出来看看"。你不用每次都在手机上跑 App,直接在电脑上就能看到效果。
现在你应该能看到:
- 浅蓝色背景 + 第 1 张图片 + 欢迎文字
- 底部有 4 个小白点,第一个高亮
- 用手(或用鼠标拖拽)向左滑动 → 翻到第 2 页,背景变深灰色
- 继续滑到第 4 页 → 出现"立即体验"按钮
- 小圆点实时跟着变化
七、完整代码
以下是完整代码,可以直接复制到 Xcode 中运行:
swift
import SwiftUI
// 扩展 Color,支持用 "#ccecff" 这样的十六进制颜色码创建颜色
extension Color {
init(hex: String) {
let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted)
var int: UInt64 = 0
Scanner(string: hex).scanHexInt64(&int)
let a, r, g, b: UInt64
switch hex.count {
case 3:
(a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17)
case 6:
(a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF)
case 8:
(a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF)
default:
(a, r, g, b) = (1, 1, 1, 0)
}
self.init(
.sRGB,
red: Double(r) / 255,
green: Double(g) / 255,
blue: Double(b) / 255,
opacity: Double(a) / 255
)
}
}
// App 首次启动引导页
struct ContentView: View {
// 图片数据集合
let images = ["ic_guide_1", "ic_guide_2", "ic_guide_3", "ic_guide_4"]
// 文案描述
let descriptions = [
"欢迎使用 Guide 引导页",
"这是基于 SwiftUI 的引导页",
"你可以自定义引导页的样式",
"开始你的 SwiftUI 之旅"
]
// 背景颜色
let backgroundColors = [
Color(hex: "#ccecff"),
Color(hex: "#56565b"),
Color(hex: "#cdebe8"),
Color(hex: "#a7e8c3")
]
// 当前页码
@State private var currentPage = 0
var body: some View {
ZStack {
// 背景颜色
backgroundColors[currentPage]
.frame(maxWidth: .infinity, maxHeight: .infinity)
.ignoresSafeArea()
// 内容区域 - 使用 TabView 实现滑动切换
TabView(selection: $currentPage) {
ForEach(images.indices, id: \.self) { index in
VStack {
Spacer()
Image(images[index])
.resizable()
.frame(width: 300, height: 300)
Text(descriptions[index])
.font(.title)
.foregroundStyle(.tint)
// 只在最后一页显示"立即体验"按钮
if images[currentPage] == images.last {
Button(action: {}) {
Text("立即体验")
.font(.system(size: 17))
.padding()
.frame(width: 180)
.background(.white)
.cornerRadius(30)
}
.padding(.vertical, 30)
}
Spacer()
}
.tag(index)
}
}
.tabViewStyle(.page)
.indexViewStyle(.page(backgroundDisplayMode: .always))
}
}
}
#Preview {
ContentView()
}
八、总结:你刚刚学会了什么
这篇文章边写代码边解释,你不知不觉已经掌握了 SwiftUI 的以下核心知识点:
| 知识点 | 用在哪里 |
|---|---|
import SwiftUI |
导入框架 |
extension |
给 Color 扩展 hex 初始化方法 |
let vs var |
准备图片、文案、颜色数据 |
@State |
记录当前页码,实现数据驱动界面刷新 |
ZStack |
层叠背景色和内容 |
TabView + .page |
分页式滑动 |
ForEach |
循环生成 4 个引导页 |
VStack + Spacer |
垂直布局 + 居中对齐 |
Image、Text、Button |
三种基础控件 |
if 条件判断 |
控制按钮仅在最后一页出现 |
#Preview |
实时预览界面 |
.frame、.padding、.cornerRadius 等修饰符 |
调整界面细节 |
🎯 记住这个万能公式:准备数据 → 定义状态 → 搭建界面。 无论是引导页还是更复杂的 App,基本都遵循这个模式。
试着改一改代码来加深理解:
- 把图片换成你自己的图,改改文字
- 修改
backgroundColors里的颜色码,看看效果 - 把 4 页改成 5 页试试
- 给按钮的
action: {}里加一行print("开始体验!")
动手改,动手跑,这才是学编程最快的方式。加油!
本文用 SwiftUI + Xcode 编写,源码基于实际项目拆解,适合零基础初学者入门实践。如有疑问,欢迎在评论区留言交流。