🛎️

TipKit 实战:使用 TipGroup 按顺序显示引导提示 (iOS 18+)

(更新于 )

核心摘要:默认情况下,TipKit 的提示显示顺序是不可控的。在 iOS 18 中,苹果引入了 TipGroup API,专门用于构建有序的 Onboarding(新手引导)流程。对于旧版本,我们可以通过自定义规则巧妙实现相同的效果。

TipKit 是帮助用户发现新功能的利器,但在构建分步教程(Step-by-step tutorial)时,我们需要严格控制提示的出现顺序。本文将介绍 iOS 18 原生方案与 iOS 17 兼容方案。

方案一:使用 TipGroup(iOS 18+ 推荐)

在 iOS 18 (以及对应的 macOS 15, iOS 26 环境) 中,TipGroup 是管理多个提示优先级的最佳方式。通过设置 .ordered 属性,系统会自动确保同一时间只显示一个提示,且前一个提示失效后才会显示下一个。

基础用法

Swift
struct Tip1: Tip {
    @Parameter static var show: Bool = false
    var title: Text { Text("第一步:点击这里") }
    var rules: [Rule] {
        #Rule(Self.$show) { $0 }
    }
}

struct Tip2: Tip {
    var title: Text { Text("第二步:查看详情") }
    // Tip2 不需要额外的 Rule,TipGroup 会自动控制它的显示时机
}

struct TipGroupDemo: View {
    // 创建一个有序的 TipGroup
    @State var tips = TipGroup(.ordered) { 
        Tip1()
        Tip2()
    }

    var body: some View {
        VStack {
            Text("Welcome")
                .popoverTip(tips.currentTip) // 自动显示当前应该显示的提示

            Button("开始引导") {
                Tip1.show = true // 激活 Tip1,Tip2 会排队等待
            }
            .buttonStyle(.bordered)
        }
    }
}

效果演示

分布式显示(跨组件)

TipGroup 不仅限于单一视图。你可以将其传递给不同的组件,实现跨视图的引导流程:

Swift
var body: some View {
    VStack(spacing: 80) {
        Text("功能模块 A")
            // 只有当 currentTip 是 Tip1 时才显示
            .popoverTip(tips.currentTip as? Tip1) 

        Text("功能模块 B")
            // 只有当 currentTip 是 Tip2 时才显示
            .popoverTip(tips.currentTip as? Tip2) 

        Button("启动教程") {
            Tip1.show = true
        }
    }
}

方案二:自定义顺序逻辑(iOS 17 兼容)

在 iOS 17 中,我们需要手动通过 @Parameterinvalidate 回调来编排顺序。核心逻辑是:当前一个 Tip 关闭时,自动修改下一个 Tip 的激活参数

实现步骤

  1. 定义协议:统一管理 Tip 的显示开关。
  2. 自定义 Style:拦截关闭操作,触发下一个 Tip。

完整代码

Swift
import SwiftUI
import TipKit

// 1. 定义统一协议
protocol ShowTip: Tip {
    static var show: Bool { get set }
}

// 2. 定义 Tip
struct Step1Tip: ShowTip {
    var title: Text = Text("第一步")
    @Parameter static var show: Bool = false
    var rules: [Rule] { [ #Rule(Self.$show) { $0 == true } ] }
}

struct Step2Tip: ShowTip {
    var title: Text = Text("第二步")
    @Parameter static var show: Bool = false
    var rules: [Rule] { [ #Rule(Self.$show) { $0 == true } ] }
}

// 3. 自定义样式,负责"接力"
struct ChainedTipStyle<NextTip: ShowTip>: TipViewStyle {
    let nextTip: NextTip.Type

    func makeBody(configuration: Configuration) -> some View {
        VStack(alignment: .leading) {
            configuration.title?.font(.headline)
            configuration.message?.font(.subheadline)
        }
        .padding()
        .background(.thinMaterial)
        .cornerRadius(10)
        .overlay(alignment: .topTrailing) {
            Button(action: {
                // 关闭当前 Tip
                configuration.tip.invalidate(reason: .tipClosed)
                // 核心:激活下一个 Tip
                nextTip.show = true 
            }) {
                Image(systemName: "xmark.circle.fill")
                    .foregroundStyle(.secondary)
            }
            .padding(8)
        }
    }
}

// 4. 使用
struct LegacySequenceDemo: View {
    let tip1 = Step1Tip()
    let tip2 = Step2Tip()

    var body: some View {
        VStack {
            Button("开始引导") { Step1Tip.show = true }
            
            Text("目标 A")
                .popoverTip(tip1)
                // 绑定 Tip1 关闭后触发 Tip2
                .tipViewStyle(ChainedTipStyle(nextTip: Step2Tip.self))
                
            Text("目标 B")
                .popoverTip(tip2)
        }
    }
}

效果演示

延伸阅读

相关提示

订阅 Fatbobman 周报

每周精选 Swift 与 SwiftUI 开发技巧,加入众多开发者的行列。

立即订阅