TipKit shows feature-discovery hints anchored to your UI. A tip is a type conforming to the Tip
protocol; show it with popoverTip or an inline TipView, and nothing appears until you call
Tips.configure() at app start.
Defining and Showing a Tip
Only title is required. An enum works well for a family of tips.
enum FeedTip: Tip {
case add
var title: Text {
switch self {
case .add: Text("Add more items.")
}
}
}
Button("Add", systemImage: "plus", action: feed.addItem)
.popoverTip(FeedTip.add)
@main
struct MyApp: App {
init() {
try? Tips.configure()
}
var body: some Scene {
WindowGroup { ContentView() }
}
}
Forgetting Tips.configure() is the classic gotcha: the code compiles and no tip ever shows.
TipView(FeedTip.add, arrowEdge: .trailing) renders the same tip inline, in a list row for
example.
Message, Image, and Actions
Optional message, image, and actions properties enrich the tip. The action handler comes
from the popoverTip or TipView call site and matches on the action id.
var actions: [Action] {
[Action(id: "add", title: "Add")]
}
Button("Add", systemImage: "plus", action: feed.addItem)
.popoverTip(FeedTip.add) { action in
if action.id == "add" {
feed.addItem()
}
}
Configuration
Tips.configure takes options. displayFrequency throttles tips (immediate, weekly,
monthly). datastoreLocation moves the tip state, which defaults to user defaults, into an app
group container so iPhone and watch stay in sync.
try? Tips.configure([
.displayFrequency(.immediate),
.datastoreLocation(.groupContainer(identifier: "com.myapp.group"))
])
UIKit and Debugging
TipKit is not SwiftUI-only: TipUIView, TipUIPopoverViewController, and
TipUICollectionViewCell cover UIKit. For testing, Tips.resetDatastore() clears state, and
Tips.showAllTipsForTesting() or Tips.hideAllTipsForTesting() force tips on or off.