tipkit
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseTipKit
TipKit
Add feature discovery tips, contextual hints, and onboarding coach marks to
iOS 17+ apps using Apple's TipKit framework. TipKit manages display frequency,
eligibility rules, and persistence so tips appear at the right time and
disappear once the user has learned the feature.
使用Apple的TipKit框架为iOS 17+应用添加功能发现提示、上下文提示以及引导操作标记。TipKit会管理提示的显示频率、资格规则和持久化状态,确保提示在合适的时机出现,并在用户熟悉功能后自动消失。
Setup
配置
Call once in , before any views render. This
initializes the tips datastore and begins rule evaluation. Calling it later
risks a race where tip views attempt to display before the datastore is ready.
Tips.configure()App.initswift
import SwiftUI
import TipKit
@main
struct MyApp: App {
init() {
try? Tips.configure([
.datastoreLocation(.applicationDefault)
])
}
var body: some Scene {
WindowGroup { ContentView() }
}
}在中、所有视图渲染之前调用一次。这会初始化提示数据存储并开始规则评估。如果延后调用,可能会出现提示视图在数据存储准备就绪前尝试显示的竞争问题。
App.initTips.configure()swift
import SwiftUI
import TipKit
@main
struct MyApp: App {
init() {
try? Tips.configure([
.datastoreLocation(.applicationDefault)
])
}
var body: some Scene {
WindowGroup { ContentView() }
}
}DatastoreLocation Options
数据存储位置选项
| Option | Use Case |
|---|---|
| Default location, app sandbox (most apps) |
| Share tips state across app and extensions |
| Custom file URL for full control over storage location |
| 选项 | 使用场景 |
|---|---|
| 默认位置,应用沙箱(大多数应用适用) |
| 在应用和扩展之间共享提示状态 |
| 自定义文件URL,完全控制存储位置 |
CloudKit Sync
CloudKit同步
Sync tip state across a user's devices so they do not see the same tip on
every device. Add the CloudKit container option alongside the datastore
location.
swift
try? Tips.configure([
.datastoreLocation(.applicationDefault),
.cloudKitContainer(.named("iCloud.com.example.app"))
])在用户的多台设备之间同步提示状态,避免用户在每台设备上都看到相同的提示。在配置数据存储位置的同时添加CloudKit容器选项。
swift
try? Tips.configure([
.datastoreLocation(.applicationDefault),
.cloudKitContainer(.named("iCloud.com.example.app"))
])Defining Tips
定义提示
Conform a struct to the protocol. Provide a at minimum.
Add for supporting detail and for a leading icon. Keep
titles short and action-oriented because the tip appears as a compact callout.
Tiptitlemessageimageswift
import TipKit
struct FavoriteTip: Tip {
var title: Text { Text("Pin Your Favorites") }
var message: Text? { Text("Tap the heart icon to save items for quick access.") }
var image: Image? { Image(systemName: "heart") }
}Properties: (required), (optional detail), (optional leading icon), (optional buttons), (optional eligibility conditions), (display frequency, max count).
titlemessageimageactionsrulesoptionsLifecycle: Pending (rules unsatisfied) -> Eligible (all rules pass) -> Invalidated (dismissed, actioned, or programmatically removed). Once invalidated, a tip does not reappear unless the datastore is reset.
让结构体遵循协议。至少需要提供。可添加作为补充说明,作为左侧图标。标题要简短且以行为导向,因为提示会以紧凑的标注形式显示。
Tiptitlemessageimageswift
import TipKit
struct FavoriteTip: Tip {
var title: Text { Text("Pin Your Favorites") }
var message: Text? { Text("Tap the heart icon to save items for quick access.") }
var image: Image? { Image(systemName: "heart") }
}属性:(必填)、(可选补充说明)、(可选左侧图标)、(可选按钮)、(可选资格条件)、(显示频率、最大显示次数)。
titlemessageimageactionsrulesoptions生命周期:待触发(规则未满足)→ 符合条件(所有规则通过)→ 失效(已关闭、已执行对应操作或通过代码移除)。提示一旦失效,除非重置数据存储,否则不会再次显示。
Displaying Tips
显示提示
Inline Tips with TipView
内嵌提示(使用TipView)
Embed a directly in your layout. It renders as a rounded card that
appears and disappears with animation. Use for tips within scrollable content.
TipViewswift
let favoriteTip = FavoriteTip()
var body: some View {
VStack {
TipView(favoriteTip)
ItemListView()
}
}将直接嵌入布局中。它会渲染为带圆角的卡片,显示和隐藏时带有动画效果。适用于滚动内容中的提示。
TipViewswift
let favoriteTip = FavoriteTip()
var body: some View {
VStack {
TipView(favoriteTip)
ItemListView()
}
}Popover Tips with .popoverTip()
弹出提示(使用.popoverTip())
Attach a tip as a popover anchored to any view. The framework draws an arrow
from the popover to the anchor. Use for tips pointing to a specific control.
swift
Button { toggleFavorite() } label: { Image(systemName: "heart") }
.popoverTip(favoriteTip)
// Control arrow direction (omit to let system choose)
.popoverTip(favoriteTip, arrowEdge: .bottom)将提示作为弹出框附加到任意视图上。框架会绘制从弹出框到锚点视图的箭头。适用于指向特定控件的提示。
swift
Button { toggleFavorite() } label: { Image(systemName: "heart") }
.popoverTip(favoriteTip)
// 控制箭头方向(省略则由系统自动选择)
.popoverTip(favoriteTip, arrowEdge: .bottom)Custom TipViewStyle
自定义TipViewStyle
Create a custom style to control tip appearance across the app. Conform
to and implement .
TipViewStylemakeBody(configuration:)swift
struct CustomTipStyle: TipViewStyle {
func makeBody(configuration: Configuration) -> some View {
HStack(spacing: 12) {
configuration.image?
.font(.title2)
.foregroundStyle(.tint)
VStack(alignment: .leading, spacing: 4) {
configuration.title
.font(.headline)
configuration.message?
.font(.subheadline)
.foregroundStyle(.secondary)
}
}
.padding()
}
}
// Apply globally or per view
TipView(favoriteTip)
.tipViewStyle(CustomTipStyle())创建自定义样式以统一控制应用内提示的外观。遵循协议并实现方法。
TipViewStylemakeBody(configuration:)swift
struct CustomTipStyle: TipViewStyle {
func makeBody(configuration: Configuration) -> some View {
HStack(spacing: 12) {
configuration.image?
.font(.title2)
.foregroundStyle(.tint)
VStack(alignment: .leading, spacing: 4) {
configuration.title
.font(.headline)
configuration.message?
.font(.subheadline)
.foregroundStyle(.secondary)
}
}
.padding()
}
}
// 全局应用或针对单个视图应用
TipView(favoriteTip)
.tipViewStyle(CustomTipStyle())Tip Rules
提示规则
Rules control when a tip becomes eligible. All rules in the array
must pass before the tip displays. TipKit supports two rule types:
parameter-based and event-based.
rules规则用于控制提示何时符合显示条件。数组中的所有规则必须通过(逻辑与),提示才会显示。TipKit支持两种规则类型:基于参数的规则和基于事件的规则。
rulesParameter-Based Rules
基于参数的规则
Use to track app state. The tip becomes eligible when the
parameter value satisfies the rule condition.
@Parameterswift
struct FavoriteTip: Tip {
@Parameter
static var hasSeenList: Bool = false
var title: Text { Text("Pin Your Favorites") }
var rules: [Rule] {
#Rule(Self.$hasSeenList) { $0 == true }
}
}
// Set the parameter when the user reaches the list
FavoriteTip.hasSeenList = true使用跟踪应用状态。当参数值满足规则条件时,提示即符合显示条件。
@Parameterswift
struct FavoriteTip: Tip {
@Parameter
static var hasSeenList: Bool = false
var title: Text { Text("Pin Your Favorites") }
var rules: [Rule] {
#Rule(Self.$hasSeenList) { $0 == true }
}
}
// 当用户进入列表页面时设置参数
FavoriteTip.hasSeenList = trueEvent-Based Rules
基于事件的规则
Use to track user actions. Donate to the event each time the
action occurs. The rule fires when the donation count or timing condition
is met. This is ideal for tips that should appear after the user has
performed an action several times without discovering a related feature.
Tips.Eventswift
struct ShortcutTip: Tip {
static let appOpenedEvent = Tips.Event(id: "appOpened")
var title: Text { Text("Try the Quick Action") }
var rules: [Rule] {
#Rule(Self.appOpenedEvent) { $0.donations.count >= 3 }
}
}
// Donate each time the app opens
ShortcutTip.appOpenedEvent.donate()使用跟踪用户操作。每次用户执行对应操作时,向事件捐赠一次。当捐赠次数或时间条件满足时,规则触发。这种规则非常适合在用户多次执行某操作但未发现相关功能时显示提示的场景。
Tips.Eventswift
struct ShortcutTip: Tip {
static let appOpenedEvent = Tips.Event(id: "appOpened")
var title: Text { Text("Try the Quick Action") }
var rules: [Rule] {
#Rule(Self.appOpenedEvent) { $0.donations.count >= 3 }
}
}
// 每次应用启动时进行事件捐赠
ShortcutTip.appOpenedEvent.donate()Combining Multiple Rules
组合多个规则
Place multiple rules in the array. All must pass (logical AND).
swift
struct AdvancedTip: Tip {
@Parameter
static var isLoggedIn: Bool = false
static let featureUsedEvent = Tips.Event(id: "featureUsed")
var title: Text { Text("Unlock Advanced Mode") }
var rules: [Rule] {
#Rule(Self.$isLoggedIn) { $0 == true }
#Rule(Self.featureUsedEvent) { $0.donations.count >= 5 }
}
}在数组中添加多个规则,所有规则必须同时通过(逻辑与)。
swift
struct AdvancedTip: Tip {
@Parameter
static var isLoggedIn: Bool = false
static let featureUsedEvent = Tips.Event(id: "featureUsed")
var title: Text { Text("Unlock Advanced Mode") }
var rules: [Rule] {
#Rule(Self.$isLoggedIn) { $0 == true }
#Rule(Self.featureUsedEvent) { $0.donations.count >= 5 }
}
}Display Frequency Options
显示频率选项
Control how often tips appear using the property.
optionsswift
struct DailyTip: Tip {
var title: Text { Text("Daily Reminder") }
var options: [TipOption] {
MaxDisplayCount(3) // Show at most 3 times total
IgnoresDisplayFrequency(true) // Bypass global frequency limit
}
}Global display frequency is set at configuration time:
swift
try? Tips.configure([
.displayFrequency(.daily) // .immediate, .hourly, .daily, .weekly, .monthly
])With , the system shows at most one tip per day across the entire
app, unless a specific tip sets .
.dailyIgnoresDisplayFrequency(true)使用属性控制提示的显示频率。
optionsswift
struct DailyTip: Tip {
var title: Text { Text("Daily Reminder") }
var options: [TipOption] {
MaxDisplayCount(3) // 最多显示3次
IgnoresDisplayFrequency(true) // 绕过全局频率限制
}
}全局显示频率在配置时设置:
swift
try? Tips.configure([
.displayFrequency(.daily) // .immediate, .hourly, .daily, .weekly, .monthly
])设置为时,系统在整个应用中每天最多显示一个提示,除非特定提示设置了。
.dailyIgnoresDisplayFrequency(true)Tip Actions
提示操作
Add action buttons to a tip for direct interaction. Each action has an
and a label. Handle the action in the tip view's action handler.
idswift
struct FeatureTip: Tip {
var title: Text { Text("Try the New Editor") }
var message: Text? { Text("We added a powerful new editing mode.") }
var actions: [Action] {
Action(id: "open-editor", title: "Open Editor")
Action(id: "learn-more", title: "Learn More")
}
}Handle actions in the view:
swift
TipView(featureTip) { action in
switch action.id {
case "open-editor":
navigateToEditor()
featureTip.invalidate(reason: .actionPerformed)
case "learn-more":
showHelpSheet = true
default:
break
}
}为提示添加操作按钮以支持直接交互。每个操作都有和标签。在提示视图的操作处理程序中处理对应操作。
idswift
struct FeatureTip: Tip {
var title: Text { Text("Try the New Editor") }
var message: Text? { Text("We added a powerful new editing mode.") }
var actions: [Action] {
Action(id: "open-editor", title: "Open Editor")
Action(id: "learn-more", title: "Learn More")
}
}在视图中处理操作:
swift
TipView(featureTip) { action in
switch action.id {
case "open-editor":
navigateToEditor()
featureTip.invalidate(reason: .actionPerformed)
case "learn-more":
showHelpSheet = true
default:
break
}
}Tip Groups
提示组
Use to coordinate multiple tips within a single view.
ensures only one tip from the group displays at a time,
preventing tip overload. Tips display in priority order.
TipGroupTipGroupswift
struct OnboardingView: View {
let tipGroup = TipGroup(.ordered) {
WelcomeTip()
NavigationTip()
ProfileTip()
}
var body: some View {
VStack {
TipView(tipGroup.currentTip!)
Button("Next") {
tipGroup.currentTip?.invalidate(reason: .actionPerformed)
}
}
}
}使用协调单个视图中的多个提示。确保同一时间仅显示组中的一个提示,避免提示过多让用户负担过重。提示会按优先级顺序显示。
TipGroupTipGroupswift
struct OnboardingView: View {
let tipGroup = TipGroup(.ordered) {
WelcomeTip()
NavigationTip()
ProfileTip()
}
var body: some View {
VStack {
TipView(tipGroup.currentTip!)
Button("Next") {
tipGroup.currentTip?.invalidate(reason: .actionPerformed)
}
}
}
}Priority Options
优先级选项
| Initializer | Behavior |
|---|---|
| Tips display in the order they are listed |
When the current tip is invalidated, the next eligible tip in the group
becomes .
currentTip| 初始化方法 | 行为 |
|---|---|
| 提示按列表顺序显示 |
当前提示失效后,组中的下一个符合条件的提示会成为。
currentTipProgrammatic Control
程序化控制
Invalidating Tips
使提示失效
Call when the user performs the discovered action or
when the tip is no longer relevant.
invalidate(reason:)swift
let tip = FavoriteTip()
tip.invalidate(reason: .actionPerformed)| Reason | When to Use |
|---|---|
| User performed the action the tip describes |
| Tip hit its maximum display count |
| User explicitly dismissed the tip |
当用户执行了提示中描述的操作,或者提示不再相关时,调用方法。
invalidate(reason:)swift
let tip = FavoriteTip()
tip.invalidate(reason: .actionPerformed)| 原因 | 使用场景 |
|---|---|
| 用户执行了提示中描述的操作 |
| 提示达到了最大显示次数 |
| 用户明确关闭了提示 |
Testing Utilities
测试工具
TipKit provides static methods to control tip visibility during development
and testing. Gate these behind or checks so they
never run in production builds.
#if DEBUGProcessInfoswift
#if DEBUG
// Show all tips regardless of rules (useful during development)
Tips.showAllTipsForTesting()
// Show only specific tips
Tips.showTipsForTesting([FavoriteTip.self, ShortcutTip.self])
// Hide all tips (useful for UI tests that do not involve tips)
Tips.hideAllTipsForTesting()
// Reset the datastore (clears all tip state, invalidations, and events)
try? Tips.resetDatastore()
#endifTipKit提供静态方法以在开发和测试期间控制提示的可见性。将这些代码放在或检查中,确保它们不会在生产构建中运行。
#if DEBUGProcessInfoswift
#if DEBUG
// 忽略所有规则显示所有提示(开发期间实用)
Tips.showAllTipsForTesting()
// 仅显示特定提示
Tips.showTipsForTesting([FavoriteTip.self, ShortcutTip.self])
// 隐藏所有提示(适用于不涉及提示的UI测试)
Tips.hideAllTipsForTesting()
// 重置数据存储(清除所有提示状态、失效记录和事件)
try? Tips.resetDatastore()
#endifUsing ProcessInfo for Test Schemes
使用ProcessInfo配置测试Scheme
swift
if ProcessInfo.processInfo.arguments.contains("--show-all-tips") {
Tips.showAllTipsForTesting()
}Pass as a launch argument in the Xcode scheme for
development builds.
--show-all-tipsswift
if ProcessInfo.processInfo.arguments.contains("--show-all-tips") {
Tips.showAllTipsForTesting()
}在Xcode的开发构建Scheme中添加作为启动参数。
--show-all-tipsCommon Mistakes
常见错误
DON'T: Call Tips.configure() anywhere except App.init
错误:在App.init以外的地方调用Tips.configure()
Calling in a view's or modifier
creates a race condition where tip views try to render before the
datastore is ready, causing missing or flickering tips.
Tips.configure()onAppeartaskswift
// WRONG
struct ContentView: View {
var body: some View {
Text("Hello")
.task { try? Tips.configure() } // Too late, views already rendered
}
}
// CORRECT
@main struct MyApp: App {
init() { try? Tips.configure() }
var body: some Scene { WindowGroup { ContentView() } }
}在视图的或修饰符中调用会导致竞争条件,提示视图可能在数据存储准备就绪前尝试渲染,导致提示缺失或闪烁。
onAppeartaskTips.configure()swift
// 错误示例
struct ContentView: View {
var body: some View {
Text("Hello")
.task { try? Tips.configure() } // 时机太晚,视图已开始渲染
}
}
// 正确示例
@main struct MyApp: App {
init() { try? Tips.configure() }
var body: some Scene { WindowGroup { ContentView() } }
}DON'T: Show too many tips at once
错误:同时显示过多提示
Displaying multiple tips simultaneously overwhelms users and dilutes the
impact of each tip. Users learn to ignore them.
swift
// WRONG: Three tips visible at the same time
VStack {
TipView(tipA)
TipView(tipB)
TipView(tipC)
}
// CORRECT: Use TipGroup to sequence them
let group = TipGroup(.ordered) { TipA(); TipB(); TipC() }
TipView(group.currentTip!)同时显示多个提示会让用户不知所措,降低每个提示的效果。用户会逐渐忽略提示。
swift
// 错误示例:同时显示三个提示
VStack {
TipView(tipA)
TipView(tipB)
TipView(tipC)
}
// 正确示例:使用TipGroup按顺序显示
let group = TipGroup(.ordered) { TipA(); TipB(); TipC() }
TipView(group.currentTip!)DON'T: Forget to invalidate tips after the user performs the action
错误:用户执行操作后未使提示失效
If a tip says "Tap the star to favorite" and the user taps the star but
the tip remains, it erodes trust in the UI.
swift
// WRONG: Tip stays visible after user acts
Button("Favorite") { toggleFavorite() }
.popoverTip(favoriteTip)
// CORRECT: Invalidate on action
Button("Favorite") {
toggleFavorite()
favoriteTip.invalidate(reason: .actionPerformed)
}
.popoverTip(favoriteTip)如果提示显示“点击星标收藏”,但用户点击星标后提示仍未消失,会降低用户对UI的信任度。
swift
// 错误示例:用户操作后提示仍可见
Button("Favorite") { toggleFavorite() }
.popoverTip(favoriteTip)
// 正确示例:操作后使提示失效
Button("Favorite") {
toggleFavorite()
favoriteTip.invalidate(reason: .actionPerformed)
}
.popoverTip(favoriteTip)DON'T: Leave testing tips enabled in production
错误:在生产环境中启用测试提示
Tips.showAllTipsForTesting()swift
// WRONG: Always active
Tips.showAllTipsForTesting()
// CORRECT: Gated behind DEBUG
#if DEBUG
Tips.showAllTipsForTesting()
#endifTips.showAllTipsForTesting()swift
// 错误示例:始终启用
Tips.showAllTipsForTesting()
// 正确示例:仅在DEBUG模式下启用
#if DEBUG
Tips.showAllTipsForTesting()
#endifDON'T: Make tip titles too long
错误:提示标题过长
Long titles get truncated or wrap awkwardly in the compact tip callout.
Put the key action in the title and supporting context in the message.
swift
// WRONG
var title: Text { Text("You can tap the heart button to save this item to your favorites list") }
// CORRECT
var title: Text { Text("Save to Favorites") }
var message: Text? { Text("Tap the heart icon to keep items for quick access.") }过长的标题会被截断或在紧凑的提示标注中换行混乱。将核心行为放在标题中,补充说明放在message里。
swift
// 错误示例
var title: Text { Text("You can tap the heart button to save this item to your favorites list") }
// 正确示例
var title: Text { Text("Save to Favorites") }
var message: Text? { Text("Tap the heart icon to keep items for quick access.") }DON'T: Use tips for critical information
错误:将关键信息放在提示中
Users can dismiss tips at any time and they do not reappear. Never put
essential instructions or safety information in a tip.
swift
// WRONG: Critical info in a dismissible tip
struct DataLossTip: Tip {
var title: Text { Text("Unsaved changes will be lost") }
}
// CORRECT: Use an alert or inline warning for critical information
// Reserve tips for feature discovery and progressive disclosure用户可以随时关闭提示,且提示关闭后不会再次显示。切勿将必要的说明或安全信息放在提示中。
swift
// 错误示例:将关键信息放在可关闭的提示中
struct DataLossTip: Tip {
var title: Text { Text("Unsaved changes will be lost") }
}
// 正确示例:使用弹窗或内嵌警告显示关键信息
// 提示仅用于功能发现和渐进式披露Review Checklist
评审检查清单
- called in
Tips.configure(), before any views renderApp.init - Each tip has a clear, concise title (action-oriented, under ~40 characters)
- Tips invalidated when the user performs the discovered action
- Rules set so tips appear at the right time (not immediately on first launch for all tips)
- used when multiple tips exist in one view
TipGroup - Testing utilities (,
showAllTipsForTesting) gated behindresetDatastore#if DEBUG - CloudKit sync configured if the app supports multiple devices
- Display frequency set appropriately (or
.dailyfor most apps).weekly - Tips used for feature discovery only, not for critical information
- Custom applied consistently if the default style does not match the app design
TipViewStyle - Tip actions handled and tip invalidated in the action handler
- Event donations placed at the correct user action points
- Ensure custom Tip types are Sendable; configure Tips on @MainActor
- 在
Tips.configure()中调用,且在所有视图渲染之前App.init - 每个提示都有清晰、简洁的标题(以行为导向,约40字符以内)
- 用户执行提示中描述的操作后,提示已被设置为失效
- 设置了合适的规则,确保提示在正确的时机显示(并非所有提示都在首次启动时立即显示)
- 单个视图中有多个提示时使用了
TipGroup - 测试工具(、
showAllTipsForTesting)仅在resetDatastore下启用#if DEBUG - 如果应用支持多设备,已配置CloudKit同步
- 设置了合适的显示频率(大多数应用使用或
.daily).weekly - 提示仅用于功能发现,未用于关键信息展示
- 如果默认样式不符合应用设计,已统一应用自定义
TipViewStyle - 提示操作已被处理,且在操作处理程序中使提示失效
- 事件捐赠已放在正确的用户操作节点
- 确保自定义Tip类型遵循Sendable;在@MainActor上配置Tips
Reference Material
参考资料
- See for complete implementation patterns including custom styles, event-based rules, tip groups, testing strategies, onboarding flows, and SwiftUI preview configuration.
references/tipkit-patterns.md
- 完整的实现模式(包括自定义样式、基于事件的规则、提示组、测试策略、引导流程和SwiftUI预览配置)请参考。
references/tipkit-patterns.md