Communitygithub.com

JordanCoin/ios-skills-collection

使用苹果 TipKit 框架实现、审查或改进应用内提示和引导。适用于添加功能发现提示、引导流程、上下文提示、首次运行体验、教练标记,或处理 Tip 协议、TipView、popoverTip、提示规则、提示事件及功能教育 UI。

ios-skills-collection 是什么?

TipKit 是苹果在 iOS 17+ 上提供的框架,用于向应用添加功能发现提示、上下文提示和引导教练标记。该技能帮助开发者有效集成 TipKit,管理提示的显示频率、资格规则和持久化状态,确保提示在合适时机出现,并在用户掌握功能后自动消失。它支持 inline TipView 和 popoverTip 两种展示方式,并提供参数规则和事件规则来控制提示触发的逻辑条件。此外,TipKit 还支持跨设备通过 CloudKit 同步提示状态,避免用户在不同设备重复看到相同提示。开发者需要先在 App.init 中调用 Tips.configure() 完成初始化,然后通过定义符合 Tip 协议的结构体来创建提示,最后在界面中嵌入 TipView 或附加 .popoverTip() 修饰符。该技能适用于 SwiftUI 应用,提供完整的从定义、展示到规则控制的解决方案。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/JordanCoin/ios-skills-collection/tree/HEAD/skills/dpearson2699--tipkit

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

TipKit

为 iOS 17+ 应用添加功能发现提示、上下文提示和引导教练标记,使用 Apple 的 TipKit 框架。TipKit 管理显示频率、资格规则和持久化,确保提示在正确的时间出现,并在用户了解该功能后消失。

目录

设置

在 App.init 中调用 Tips.configure(),在所有视图渲染之前。这将初始化提示数据存储并开始规则评估。如果在之后调用,可能会导致提示视图在数据存储准备好之前尝试显示,产生竞态条件。

import SwiftUI
import TipKit

@main
struct MyApp: App {
    init() {
        try? Tips.configure([
            .datastoreLocation(.applicationDefault)
        ])
    }

    var body: some Scene {
        WindowGroup { ContentView() }
    }
}

数据存储位置选项

选项使用场景
.applicationDefault默认位置,应用沙盒(大多数应用)
.groupContainer(identifier:)在应用和扩展之间共享提示状态
.url(_:)自定义文件 URL,完全控制存储位置

CloudKit 同步

在用户的设备之间同步提示状态,避免用户在每个设备上看到相同的提示。将 CloudKit 容器选项与数据存储位置选项一起添加。

try? Tips.configure([
    .datastoreLocation(.applicationDefault),
    .cloudKitContainer(.named("iCloud.com.example.app"))
])

定义提示

使一个结构体遵循 Tip 协议。至少提供 title。添加 message 作为支持详情,image 作为前导图标。保持标题简短且以行动为导向,因为提示会以紧凑的标注形式出现。

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") }
}

属性:title(必需),message(可选详情),image(可选前导图标),actions(可选按钮),rules(可选资格条件),options(显示频率、最大次数)。

生命周期:待定(规则未满足)-> 符合条件(所有规则通过)-> 失效(被关闭、触发操作或程序化移除)。一旦失效,除非重置数据存储,否则提示不会再次出现。

显示提示

使用 TipView 内联显示提示

直接在布局中嵌入 TipView。它会渲染为一个圆角卡片,并以动画方式出现和消失。适用于滚动内容中的提示。

let favoriteTip = FavoriteTip()
var body: some View {
    VStack {
        TipView(favoriteTip)
        ItemListView()
    }
}

使用 .popoverTip() 弹出提示

将提示作为弹出窗口附着到任何视图上。框架会从弹出窗口绘制一个指向锚点的箭头。适用于指向特定控件的提示。

Button { toggleFavorite() } label: { Image(systemName: "heart") }
    .popoverTip(favoriteTip)

// 控制箭头方向(省略则让系统选择)
.popoverTip(favoriteTip, arrowEdge: .bottom)

自定义 TipViewStyle

创建自定义样式以控制整个应用中的提示外观。遵循 TipViewStyle 协议并实现 makeBody(configuration:)。

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())

提示规则

规则控制提示何时符合条件。rules 数组中的所有规则都必须通过,提示才能显示。TipKit 支持两种规则类型:基于参数和基于事件。

基于参数的规则

使用 @Parameter 追踪应用状态。当参数值满足规则条件时,提示符合条件。

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 = true

基于事件的规则

使用 Tips.Event 追踪用户操作。每次操作发生时捐赠事件。当捐赠次数或时间条件满足时,规则触发。适用于希望在用户执行操作数次但未发现相关功能后出现的提示。

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()

组合多个规则

将多个规则放入数组。所有规则都必须通过(逻辑与)。

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 }
    }
}

显示频率选项

使用 options 属性控制提示出现的频率。

struct DailyTip: Tip {
    var title: Text { Text("Daily Reminder") }

    var options: [TipOption] {
        MaxDisplayCount(3)                   // 最多显示3次
        IgnoresDisplayFrequency(true)        // 忽略全局频率限制
    }
}

全局显示频率在配置时设置:

try? Tips.configure([
    .displayFrequency(.daily)  // .immediate, .hourly, .daily, .weekly, .monthly
])

使用 .daily 时,整个应用每天最多显示一个提示,除非某个提示设置了 IgnoresDisplayFrequency(true)。

提示动作

为提示添加操作按钮以实现直接交互。每个操作都有一个 id 和标签。在提示视图的操作处理程序中处理该操作。

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")
    }
}

在视图中处理操作:

TipView(featureTip) { action in
    switch action.id {
    case "open-editor":
        navigateToEditor()
        featureTip.invalidate(reason: .actionPerformed)
    case "learn-more":
        showHelpSheet = true
    default:
        break
    }
}

提示组

使用 TipGroup 协调单个视图中的多个提示。TipGroup 确保一次只显示组中的一个提示,避免提示过载。提示按优先级顺序显示。

struct OnboardingView: View {
    let tipGroup = TipGroup(.ordered) {
        WelcomeTip()
        NavigationTip()
        ProfileTip()
    }

    var body: some View {
        VStack {
            if let currentTip = tipGroup.currentTip {
                TipView(currentTip)
            }

            Button("Next") {
                tipGroup.currentTip?.invalidate(reason: .actionPerformed)
            }
        }
    }
}

优先级选项

初始化器行为
.ordered提示按列出的顺序显示

当前提示失效后,组中的下一个符合条件的提示会成为 currentTip。

程序化控制

使提示失效

当用户执行发现的操作或提示不再相关时,调用 invalidate(reason:)。

let tip = FavoriteTip()
tip.invalidate(reason: .actionPerformed)
原因何时使用
.actionPerformed用户执行了提示描述的操作
.displayCountExceeded提示达到最大显示次数
.tipClosed用户明确关闭了提示

测试工具

TipKit 提供了静态方法,可在开发和测试期间控制提示可见性。使用 #if DEBUG 或 ProcessInfo 检查来保护它们,避免在生产构建中运行。

#if DEBUG
// 显示所有提示,忽略规则(开发时有用)
Tips.showAllTipsForTesting()

// 只显示特定提示
Tips.showTipsForTesting([FavoriteTip.self, ShortcutTip.self])

// 隐藏所有提示(用于不涉及提示的 UI 测试)
Tips.hideAllTipsForTesting()

// 重置数据存储(清除所有提示状态、失效和事件)
try? Tips.resetDatastore()
#endif

使用 ProcessInfo 进行测试方案

if ProcessInfo.processInfo.arguments.contains("--show-all-tips") {
    Tips.showAllTipsForTesting()
}

在开发构建的 Xcode 方案中将 --show-all-tips 作为启动参数传递。

常见错误

不要:在 App.init 之外的任何地方调用 Tips.configure()

在视图的 onAppear 或 task 修饰符中调用 Tips.configure() 会产生竞态条件,提示视图在数据存储准备好之前尝试渲染,导致提示缺失或闪烁。

// 错误
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() } }
}

不要:同时显示太多提示

同时显示多个提示会让用户感到不知所措,并削弱每个提示的影响力。用户会学会忽略它们。

// 错误:三个提示同时可见
VStack {
    TipView(tipA)
    TipView(tipB)
    TipView(tipC)
}

// 正确:使用 TipGroup 排序
let group = TipGroup(.ordered) { TipA(); TipB(); TipC() }
if let currentTip = group.currentTip {
    TipView(currentTip)
}

不要:忘记在用户执行操作后使提示失效

如果提示说“点击星星收藏”并且用户点击了星星,但提示仍然存在,这会削弱用户对 UI 的信任。

// 错误:用户操作后提示仍然可见
Button("Favorite") { toggleFavorite() }
    .popoverTip(favoriteTip)

// 正确:操作时失效
Button("Favorite") {
    toggleFavorite()
    favoriteTip.invalidate(reason: .actionPerformed)
}
.popoverTip(favoriteTip)

不要:在生产中保留测试提示启用

Tips.showAllTipsForTesting() 会绕过所有规则和频率限制。在生产中发布此功能意味着每个用户都会立即看到每个提示。

// 错误:始终激活
Tips.showAllTipsForTesting()

// 正确:受 DEBUG 保护
#if DEBUG
Tips.showAllTipsForTesting()
#endif

不要:让提示标题过长

过长的标题会在紧凑的提示标注中被截断或显示错乱。将关键操作放在标题中,将支持性上下文放在消息中。

// 错误
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.") }

不要:使用提示来传递关键信息

用户可以随时关闭提示,且它们不会重新出现。切勿将基本说明或安全信息放在提示中。

// 错误:关键信息放在可关闭的提示中
struct DataLossTip: Tip {
    var title: Text { Text("Unsaved changes will be lost") }
}

// 正确:使用警告或内联警告传递关键信息
// 将提示用于功能发现和渐进式披露

审查清单

  • Tips.configure() 在 App.init 中调用,在所有视图渲染之前
  • 每个提示都有一个清晰简明的标题(以行动为导向,约40个字符以内)
  • 当用户执行发现的操作时,提示被失效
  • 规则设置合理,使提示在正确的时间出现(并非所有提示都在首次启动时立即出现)
  • 当单个视图中存在多个提示时,使用 TipGroup
  • 测试工具(showAllTipsForTesting、resetDatastore)受 #if DEBUG 保护
  • 如果应用支持多个设备,配置了 CloudKit 同步
  • 适当设置显示频率(大多数应用使用 .daily 或 .weekly)
  • 提示仅用于功能发现,不用于关键信息
  • 如果默认样式与应用设计不匹配,一致地应用自定义 TipViewStyle
  • 提示动作已处理,并在动作处理程序中使提示失效
  • 事件捐赠放置在正确的用户操作点
  • 确保自定义 Tip 类型是 Sendable;在 @MainActor 上配置 Tips

参考

  • 参见 references/tipkit-patterns.md 获取完整的实现模式,包括自定义样式、基于事件的规则、提示组、测试策略、引导流程和 SwiftUI 预览配置。

Individual skills in this repo

This repo contains 11 individual skills — each has its own dedicated page.

JordanCoin/ios-skills-collection

Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries. Use when the user asks about GitHub issues, pull requests, workflows, or wants to interact with GitHub repositories from the command line — including tasks like check CI status, create PR, list issues, or query the GitHub API.

JordanCoin/ios-skills-collection

Create media playback experiences using AVKit. Use when adding video players with AVPlayerViewController, enabling Picture-in-Picture, routing media with AirPlay, using SwiftUI VideoPlayer views, configuring transport controls, displaying subtitles and closed captions, or integrating AVFoundation playback with system UI.

JordanCoin/ios-skills-collection

Integrate and optimize Core ML models in iOS apps for on-device machine learning inference. Covers model loading (.mlmodelc, .mlpackage), predictions with auto-generated classes and MLFeatureProvider, compute unit configuration (CPU, GPU, Neural Engine), MLTensor, VNCoreMLRequest, MLComputePlan, multi-model pipelines, and deployment strategies. Use when loading Core ML models, making predictions, configuring compute units, or profiling model performance.

JordanCoin/ios-skills-collection

Implement, review, or improve maps and location features in iOS/macOS apps using MapKit and CoreLocation. Use when working with Map views, annotations, markers, polylines, user location tracking, geocoding, reverse geocoding, search/autocomplete, directions and routes, geofencing, region monitoring, CLLocationUpdate async streams, or location authorization flows. Also use when working with maps, coordinates, addresses, places, directions, distance calculations, or location-based features in Swift apps.

JordanCoin/ios-skills-collection

Display and manipulate PDF documents using PDFKit. Use when embedding PDFView to show PDF files, creating or modifying PDFDocument instances, adding annotations (highlights, notes, signatures), extracting text with PDFSelection, navigating pages, generating thumbnails, filling PDF forms, or wrapping PDFView in SwiftUI.

JordanCoin/ios-skills-collection

When the user wants to optimize their Google Play Store listing — title, short description, full description, keywords, ratings, or Play Store-specific features. Use when the user mentions "Google Play", "Android", "Play Store", "Play Console", "short description", "full description indexed", "Google Play ASO", or wants Google Play-specific keyword, creative, or ratings strategy. For iOS App Store optimization, see aso-audit and metadata-optimization.

JordanCoin/ios-skills-collection

When the user wants to implement, optimize, or use App Clips for app discovery and conversion. Use when the user mentions "App Clip", "app clip code", "mini app", "instant app", "App Clip card", "App Clip link", "no download required", "instant experience", or wants to understand how App Clips appear in App Store search. For general App Store discoverability, see aso-audit. For marketing campaigns, see ua-campaign.

JordanCoin/ios-skills-collection

When the user wants to plan a launch strategy for a new app or major update. Also use when the user mentions "app launch", "launch plan", "launch checklist", "pre-launch", "launch day", or "how to launch my app". For ongoing ASO after launch, see aso-audit. For paid acquisition during launch, see ua-campaign.

JordanCoin/ios-skills-collection

When the user wants to analyze their own app's actual performance data from App Store Connect — real downloads, revenue, IAP, subscriptions, trials, or country breakdowns synced via Appeeky Connect. Use when the user asks about "my downloads", "my revenue", "how is my app performing", "ASC data", "sales and trends", "my subscription numbers", "App Store Connect metrics", or wants to compare periods or top markets. For third-party app estimates, see app-analytics. For subscription analytics depth, see monetization-strategy.

JordanCoin/ios-skills-collection

When the user wants a full ASO health audit, review their App Store listing quality, or diagnose why their app isn't ranking. Also use when the user mentions "ASO audit", "ASO score", "why am I not ranking", "listing review", or "optimize my app store page". For keyword-specific research, see keyword-research. For metadata writing, see metadata-optimization.

JordanCoin/ios-skills-collection

Always use when working on any iOS, macOS, Swift, SwiftUI, Xcode, or Apple platform project. Routes to the correct specialized skills based on development phase and task context. This collection contains 210 skills from 28 authors covering the full iOS lifecycle.

相关技能