SwiftUI入门 10 分钟学会做一个 App 引导页

🚀 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
关键概念解释

letvar

  • let:这个值不会变(常量),比如引导页就 4 张图,不会多也不会少
  • var:这个值会变(变量),比如用户滑到哪一页是不断变化的

@State 是做什么的?

这是 SwiftUI 里非常重要的概念。@State 的意思是:

这个值发生变化时,界面自动跟着刷新。

举个例子:当用户从第 1 张图滑到第 2 张图,currentPage0 变成 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, 3
  • id: \.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 - 1currentPage == 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. 浅蓝色背景 + 第 1 张图片 + 欢迎文字
  2. 底部有 4 个小白点,第一个高亮
  3. 用手(或用鼠标拖拽)向左滑动 → 翻到第 2 页,背景变深灰色
  4. 继续滑到第 4 页 → 出现"立即体验"按钮
  5. 小圆点实时跟着变化

七、完整代码

以下是完整代码,可以直接复制到 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 垂直布局 + 居中对齐
ImageTextButton 三种基础控件
if 条件判断 控制按钮仅在最后一页出现
#Preview 实时预览界面
.frame.padding.cornerRadius 等修饰符 调整界面细节

🎯 记住这个万能公式:准备数据 → 定义状态 → 搭建界面。 无论是引导页还是更复杂的 App,基本都遵循这个模式。

试着改一改代码来加深理解:

  • 把图片换成你自己的图,改改文字
  • 修改 backgroundColors 里的颜色码,看看效果
  • 把 4 页改成 5 页试试
  • 给按钮的 action: {} 里加一行 print("开始体验!")

动手改,动手跑,这才是学编程最快的方式。加油!


本文用 SwiftUI + Xcode 编写,源码基于实际项目拆解,适合零基础初学者入门实践。如有疑问,欢迎在评论区留言交流。

相关推荐
90后的晨仔3 小时前
SwiftUI 完全指南:从声明式 UI 到响应式架构的终点回顾
ios
90后的晨仔3 小时前
SwiftUI 多线程与并发编程深度总结
ios
90后的晨仔3 小时前
Combine 与系统框架集成:将响应式编程融入 Apple 生态
ios
90后的晨仔3 小时前
Combine 与 Swift Concurrency:响应式与并发的完美协奏
ios
90后的晨仔3 小时前
Combine 自定义 Subject:构建专属的响应式事件源
ios
90后的晨仔3 小时前
Combine 架构模式:构建响应式应用的蓝图
ios
90后的晨仔3 小时前
Combine 高级实践:多线程调度、调试与测试
ios
人月神话Lee6 小时前
【图像处理】饱和度——颜色的浓淡与灰度化
ios·ai编程·图像识别
王飞飞不会飞6 小时前
iOS卡顿查找和定位-ProFile
ios·性能优化