Loading...
Loading...
Implement, review, or improve in-app tips and onboarding using Apple's TipKit framework. Use when adding feature discovery tooltips, onboarding flows, contextual tips, first-run experiences, or coach marks to iOS/macOS/visionOS apps. Trigger when working with Tip protocol, TipView, popoverTip, tip rules, tip events, or feature education UI.
npx skill4agent add dpearson2699/swift-ios-skills tipkitTips.configure()App.initimport SwiftUI
import TipKit
@main
struct MyApp: App {
init() {
try? Tips.configure([
.datastoreLocation(.applicationDefault)
])
}
var body: some Scene {
WindowGroup { ContentView() }
}
}| 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 |
try? Tips.configure([
.datastoreLocation(.applicationDefault),
.cloudKitContainer(.named("iCloud.com.example.app"))
])Tiptitlemessageimageimport 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") }
}titlemessageimageactionsrulesoptionsTipViewlet favoriteTip = FavoriteTip()
var body: some View {
VStack {
TipView(favoriteTip)
ItemListView()
}
}Button { toggleFavorite() } label: { Image(systemName: "heart") }
.popoverTip(favoriteTip)
// Control arrow direction (omit to let system choose)
.popoverTip(favoriteTip, arrowEdge: .bottom)TipViewStylemakeBody(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()
}
}
// Apply globally or per view
TipView(favoriteTip)
.tipViewStyle(CustomTipStyle())rules@Parameterstruct 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 = trueTips.Eventstruct 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()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 }
}
}optionsstruct 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
}
}try? Tips.configure([
.displayFrequency(.daily) // .immediate, .hourly, .daily, .weekly, .monthly
]).dailyIgnoresDisplayFrequency(true)idstruct 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
}
}TipGroupTipGroupstruct OnboardingView: View {
let tipGroup = TipGroup(.ordered) {
WelcomeTip()
NavigationTip()
ProfileTip()
}
var body: some View {
VStack {
TipView(tipGroup.currentTip!)
Button("Next") {
tipGroup.currentTip?.invalidate(reason: .actionPerformed)
}
}
}
}| Initializer | Behavior |
|---|---|
| Tips display in the order they are listed |
currentTipinvalidate(reason:)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 |
#if DEBUGProcessInfo#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()
#endifif ProcessInfo.processInfo.arguments.contains("--show-all-tips") {
Tips.showAllTipsForTesting()
}--show-all-tipsTips.configure()onAppeartask// 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() } }
}// 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!)// 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)Tips.showAllTipsForTesting()// WRONG: Always active
Tips.showAllTipsForTesting()
// CORRECT: Gated behind DEBUG
#if DEBUG
Tips.showAllTipsForTesting()
#endif// 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.") }// 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 disclosureTips.configure()App.initTipGroupshowAllTipsForTestingresetDatastore#if DEBUG.daily.weeklyTipViewStylereferences/tipkit-patterns.md