TipKit is Apple's framework for showing contextual tips in your app — the little callouts that point at a button or feature a user hasn't discovered yet. It's been around since iOS 17, and I keep meaning to write about it because it's genuinely one of those frameworks that's easier to use than you'd expect.

Defining a tip

Every tip is a struct that conforms to Tip. You give it a title, optionally a message and an image, and TipKit handles the rest — including deciding whether and when to show it.

import TipKit

struct FavoriteTip: Tip {
    var title: Text {
        Text("Add to Favorites")
    }

    var message: Text? {
        Text("Tap the star icon to save items for later.")
    }

    var image: Image? {
        Image(systemName: "star")
    }
}

Showing a tip

There are two ways to present a tip: inline (shifts content down) or a popover (floats over a view). For most cases in SwiftUI, the popover style is what you want.

struct ContentView: View {
    private let favoriteTip = FavoriteTip()

    var body: some View {
        Button {
            // add to favorites
        } label: {
            Image(systemName: "star")
        }
        .popoverTip(favoriteTip)
    }
}

The inline variant uses TipView directly, which is handy if you want the tip to push other content rather than float on top of it.

VStack {
    TipView(favoriteTip)
    // rest of your content
}

Configuring TipKit

You need to call Tips.configure somewhere early in your app — typically in the App init or in @main. Without this, tips won't appear.

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

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

During development it's useful to reset tips on each launch so you can see them every time without uninstalling the app:

try? Tips.configure([
    .displayFrequency(.immediate),
    .datastoreLocation(.applicationDefault)
])

Note: displayFrequency(.immediate) shows tips every launch. Don't ship this — it's for testing only.

Rules and eligibility

TipKit has a built-in rule system so tips only show up when it makes sense. You can gate on a simple boolean, on how many times a user has performed an action, or on a parameter that your app updates.

struct FavoriteTip: Tip {
    @Parameter
    static var hasViewedItems: Bool = false

    var title: Text { Text("Add to Favorites") }
    var message: Text? { Text("Tap the star to save items for later.") }

    var rules: [Rule] {
        #Rule(Self.$hasViewedItems) { $0 == true }
    }
}

Then somewhere in your app, when the user has looked at a few items:

FavoriteTip.hasViewedItems = true

The tip will only show once that parameter flips. This keeps tips from appearing for users who clearly already know what they're doing.

Events

For frequency-based eligibility, use Tips.Event. This tracks how many times something happens and lets you show a tip only after a certain threshold.

struct FavoriteTip: Tip {
    static let itemViewedEvent = Event(id: "item-viewed")

    var title: Text { Text("Add to Favorites") }

    var rules: [Rule] {
        #Rule(Self.itemViewedEvent) { $0.donations.count >= 3 }
    }
}

// call this each time the user views an item
await FavoriteTip.itemViewedEvent.donate()

After three item views, the tip becomes eligible. Combined with TipKit's default displayFrequency of .daily, it'll only show once per day — which feels much less intrusive than shouting at users on first launch.

Dismissing a tip

Tips dismiss themselves when the user taps the close button. If you want to dismiss programmatically (say, the user performed the action the tip was describing), call invalidate:

favoriteTip.invalidate(reason: .actionPerformed)

Wrap up

TipKit takes about ten minutes to add to a project and handles the annoying parts of onboarding tips — persistence, frequency, eligibility — without any custom infrastructure. If you've been putting off adding hints to your app because it felt like too much plumbing, this is worth a look.

I added it to Calendo to nudge users toward a gesture that's easy to miss, and the setup was genuinely straightforward.

Resources:

Read more

Share


Share Bluesky Mastodon Twitter LinkedIn Facebook