关于 tipkit
面向 Apple 平台应用开发者的 TipKit 技能,用于添加情境化的功能发现提示。内容涵盖呈现方式、资格规则、状态保存、测试与 API 可用条件,帮助控制提示的简短程度和触发时机。
主要功能
- 01内联和弹出式提示呈现
- 02资格规则与事件记录
- 03显示次数限制与提示失效
- 04提示分组与顺序展示
- 05CloudKit 和 App Group 状态配置
- 06测试覆盖与 API 可用性指导
适用场景
- 01在新功能附近说明其操作方式
- 02仅在指定用户操作后显示提示
- 03按顺序呈现多个功能发现提示
- 04在调试版本中验证提示显示条件
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dpearson2699/ swift-ios-skills --skill tipkit安装后在 Claude Code 中输入/tipkit确认已加载。
附带文件evals/evals.jsonreferences/tipkit-patterns.md
详细介绍
TipKit
使用 TipKit 展示小巧、契合情境的功能发现提示:内嵌提示、弹出式提示、受规则控制的引导,以及轻量的操作指引标记。通用的 SwiftUI 架构、导航、布局和较长的首次运行引导流程,请交由相关技能处理,除非 TipKit 的展示方式本身是核心问题。
目录
可用性
TipKit 的核心功能 Tip、TipView、popoverTip、规则、事件、选项和测试覆盖项,适用于 iOS 17+、iPadOS 17+、macOS 14+、tvOS 17+、watchOS 10+ 和 visionOS 1+。
明确限制较新 API 的使用范围:
| API | 可用性 | 用途 |
|---|---|---|
TipGroup | iOS 18+ | 对提示进行分组或排序;请参照提示组中的决策。 |
.cloudKitContainer(...) | iOS 18+ | 在设备间同步提示状态、参数、事件和展示次数。 |
MaxDisplayDuration | iOS 18+ | 根据累计展示时长自动使提示失效。 |
resetEligibility() | iOS 26+ | 在不重置数据存储的情况下,让此前已失效的提示重新符合展示条件。 |
配置 TipKit
在应用初始化期间调用一次 Tips.configure(_:),且必须在任何提示展示之前完成。不要从视图的 onAppear 或 .task 配置 TipKit。
import SwiftUI
import TipKit
@main
struct MyApp: App {
init() {
do {
try Tips.configure([
.datastoreLocation(.applicationDefault),
.displayFrequency(.daily)
])
} catch {
assertionFailure("TipKit configuration failed: \(error)")
}
}
var body: some Scene {
WindowGroup { ContentView() }
}
}
仅当应用与扩展或应用组成员需要有意共享提示状态时,才使用 .datastoreLocation(.groupContainer(identifier:))。请确保应用组成员的选项设置一致,因为 TipKit 会将选项状态与提示记录一同持久化。
CloudKit 同步
仅在 iOS 18+ 及更高版本中使用 CloudKit 同步。启用 iCloud + CloudKit 和 Background Modes > Remote notifications,然后传入容器:
try Tips.configure([
.cloudKitContainer(.named("iCloud.com.example.app.tips"))
])
建议使用带有 .tips 后缀的专用容器。如果存在已授权的 .tips 容器,.automatic 会优先使用该容器,否则回退到主容器。
设计优质提示
提示是简短、临时的帮助信息。用于介绍人们只需几个简单步骤就能理解并尝试的功能。如果流程需要长篇解释、多个屏幕,或涉及关键安全/错误信息,请改用教程、警告框、内嵌警告或引导流程。
遵循与 HIG 一致的默认做法:
- 标题简短、直接,并以行动为导向。
- 使用一到两句话;避免宣传性或无关的文案。
- 将提示放在所说明功能的附近。
- 如果隐藏附近的 UI 会打断任务,优先使用内嵌提示。
- 如果保持当前布局很重要,且提示可以指向特定控件,优先使用弹出式提示。
- 使用规则和展示频率,确保只有合适的用户会看到各条提示。
- 如果弹出式提示已经指向某个图标,就避免在提示中重复该图标。
定义提示
Tip 遵循 Identifiable 和 Sendable。至少提供 title;仅在能改善功能发现体验时,才添加 message、image、actions、rules、options 和 id。
import TipKit
struct FavoriteTip: Tip {
var title: Text { Text("Save to Favorites") }
var message: Text? { Text("Tap the heart to keep items for quick access.") }
var image: Image? { Image(systemName: "heart.fill") }
}
默认情况下,TipKit 会将提示类型名称用作 id。对于需要根据内容区分持久化状态的可复用提示,请重写 id:
struct NewItemTip: Tip {
let itemID: Item.ID
var id: String { "NewItemTip-\(itemID)" }
var title: Text { Text("New Item Available") }
}
使用稳定、明确的标识符。不要根据临时文案或不稳定的排序生成 ID。
展示提示
使用 TipView 展示内嵌提示:
let favoriteTip = FavoriteTip()
VStack {
TipView(favoriteTip, arrowEdge: .bottom)
ItemListView()
}
如果提示需要指向某个控件,请使用 .popoverTip:
Button {
toggleFavorite()
favoriteTip.invalidate(reason: .actionPerformed)
} label: {
Image(systemName: "heart")
}
.popoverTip(favoriteTip, arrowEdge: .top)
规则与事件
规则之间采用 AND 逻辑。只有所有规则都通过后,提示才符合显示条件。
使用 @Parameter 表示持久化的应用状态:
struct FavoriteTip: Tip {
@Parameter static var hasSeenList = false
var title: Text { Text("Save to Favorites") }
var rules: [Rule] {
#Rule(Self.$hasSeenList) { $0 == true }
}
}
使用 Tips.Event 表示重复的用户操作。TipKit 默认会查询最近 1000 条捐赠记录,因此事件规则应设置明确的边界,并谨慎使用。
struct ShortcutTip: Tip {
static let manualSaveEvent = Tips.Event(id: "manualSave")
var title: Text { Text("Save Faster") }
var rules: [Rule] {
#Rule(Self.manualSaveEvent) {
$0.donations.donatedWithin(.week).count >= 3
}
}
}
ShortcutTip.manualSaveEvent.sendDonation()
若要定义更丰富的事件规则,可定义 Tips.Event<DonationInfo>,其中
DonationInfo: Codable, Sendable。捐赠载荷应保持精简。
当多个提示使用相同事件时,将相关事件定义归入共享命名空间;事件 ID 是持久化边界,因此 ID 冲突可能导致符合显示条件的情况难以理解。
选项与失效
谨慎使用选项;显示频率和失效规则都是提示持久化行为的一部分。
struct DailyTip: Tip {
var title: Text { Text("Try Filters") }
var options: [any TipOption] {
MaxDisplayCount(3)
IgnoresDisplayFrequency(false)
}
}
MaxDisplayDuration 适用于 iOS 18+。它会累计显示时间,并设定自动失效前的最短连续显示时长。当应用知道用户已完成所教的操作或有序步骤时,不要用它代替显式调用 invalidate(reason:)。
当用户执行已发现的操作,或提示不再相关时,调用 invalidate(reason:)。失效是永久性的,直到重置 datastore,或者在 iOS 26+ 中由特定提示调用 await resetEligibility()。
favoriteTip.invalidate(reason: .actionPerformed)
显式关闭时使用 .tipClosed;只有在描述自动失效的结果时,才使用 .displayCountExceeded 或 .displayDurationExceeded。
操作与样式
当用户需要直接前往设置、了解更多信息或进入设置流程时,添加 Action 按钮。
struct FeatureTip: Tip {
var title: Text { Text("Try the New Editor") }
var actions: [Action] {
Action(id: "open-editor", title: "Open Editor")
Action(id: "learn-more", title: "Learn More")
}
}
TipView(FeatureTip()) { action in
switch action.id {
case "open-editor":
openEditor()
case "learn-more":
showHelp()
default:
break
}
}
自定义外观时,优先使用 TipViewStyle.Configuration 的值,而不是直接读取具体提示实例。这样可以保留应用于 TipView 的标签、处理程序和修饰符。
struct CompactTipStyle: TipViewStyle {
func makeBody(configuration: Configuration) -> some View {
HStack(alignment: .top) {
configuration.image?
VStack(alignment: .leading) {
configuration.title?
configuration.message?
ForEach(configuration.actions) { action in
Button(action: action.handler) {
action.label()
}
}
}
}
.padding()
}
}
提示组
TipGroup 适用于 iOS 18+。将提示组存储在 SwiftUI 状态中,使可观察的组对象在视图更新期间保持不变。每次评审 TipGroup(.ordered) 方案时,都应明确区分默认优先级与有序序列:TipGroup 默认为 .firstAvailable;若要求每条后续提示都必须等待前面所有提示失效,则必须使用 TipGroup(.ordered)。
struct OnboardingView: View {
@State private var tips = TipGroup(.ordered) {
WelcomeTip()
SearchTip()
FilterTip()
}
var body: some View {
VStack {
TipView(tips.currentTip)
ContentView()
}
}
}
MaxDisplayDuration 可以限制显示时长,但它不是有序组的排序机制。当同一提示组涉及多个控件时,应转换 currentTip 的类型:
Button("Search") { openSearch() }
.popoverTip(tips.currentTip as? SearchTip)
测试
仅在调试或测试代码中使用测试覆盖项,并在调用 Tips.configure(_:) 之前应用。
#if DEBUG
if ProcessInfo.processInfo.arguments.contains("--reset-tips") {
try? Tips.resetDatastore()
}
if ProcessInfo.processInfo.arguments.contains("--show-all-tips") {
Tips.showAllTipsForTesting()
}
#endif
try Tips.configure()
也可以使用内置启动参数:
-com.apple.TipKit.ResetDatastore 1-com.apple.TipKit.ShowAllTips 1-com.apple.TipKit.ShowTips TipTypeA,TipTypeB-com.apple.TipKit.HideAllTips 1
测试覆盖项的优先顺序是:指定显示、指定隐藏、全部显示,最后是全部隐藏。必须在调用 Tips.configure(_:) 之前运行 Tips.resetDatastore()。
常见错误
不要:从视图中配置 TipKit
在应用初始化期间进行配置。视图级别的配置可能与提示显示发生竞争,也可能触发 datastore 已配置错误。
不要:将 iOS 18+ API 当作 iOS 17 的指导
对 TipGroup、CloudKit 同步和 MaxDisplayDuration 做版本限制。对于组优先级,请采用规范的提示组决策。
不要:将提示用于关键信息
提示可以关闭,且用于提供教育性信息。对于安全问题、错误、数据丢失和必需步骤,应使用警告、确认提示、内联警告或阻断式 UI。
不要:发布测试覆盖项
showAllTipsForTesting() 及相关覆盖项会绕过规则和频率限制。请将它们放在 #if DEBUG、测试方案参数或仅用于 UI 测试的启动参数之后。
不要:使用不稳定的可复用提示 ID
提示 ID 决定持久化状态。如果可复用提示的 ID 意外发生变化,用户可能会看到重复或过时的说明。
审查清单
- 在显示提示之前,于应用初始化期间调用一次
Tips.configure(_:)。 - 仅在配置之前调用
Tips.resetDatastore(),且仅用于测试/调试。 - 为 iOS 18+ 和 iOS 26+ 的 TipKit API 添加可用性检查或回退说明。
- 提示文案简短、贴合上下文、可操作,且不带宣传性质。
- 内嵌式与弹出式展示方式符合周围 UI 流程。
- 规则针对预期受众,且不会在首次启动时显示所有提示。
- 事件 ID 保持稳定,共享时使用命名空间,捐赠的负载保持精简。
- 可复用的提示通过基于稳定内容的值覆盖
id。 - 用户执行所教操作后,使提示失效。
- 将
TipGroup保存在@State中,并遵循 Tip Groups 优先级决策。 - CloudKit 同步使用 iCloud + CloudKit、Remote notifications,并在适当情况下使用专用容器。
- 自定义样式使用
configuration值,并调用action.label()。 - 测试覆盖仅用于调试/测试,绝不在生产环境中启用。
参考资料
- 阅读 references/tipkit-patterns.md,了解完整的实现模式:自定义样式、带捐赠值的事件规则、TipGroup 排序、CloudKit/应用组持久化、可复用 ID、预览和测试启动策略。
- Apple TipKit 文档:https://sosumi.ai/documentation/tipkit
- Apple
Tips.configure(_:):https://sosumi.ai/documentation/tipkit/tips/configure(_:) - Apple
TipGroup:https://sosumi.ai/documentation/tipkit/tipgroup - Apple HIG「提供帮助」:https://sosumi.ai/design/human-interface-guidelines/offering-help
- WWDC24「使用 TipKit 自定义功能发现」:https://sosumi.ai/videos/play/wwdc2024/10070
- WWDC23「使用 TipKit 让功能更易于发现」:https://sosumi.ai/videos/play/wwdc2023/10229