Using TipKit to display a tip to the user in SwiftUI (iOS 17, WWDC 2023)

Using TipKit to display inline tip, popover floating tip, rule based tip, or counter based tip, provide custom action buttons. TipView, Tip, rules, Swift Macros

Using TipKit to display a tip to the user in SwiftUI (iOS 17, WWDC 2023)
Using TipKit to display a tip to the user in SwiftUI (iOS 17, WWDC 2023)
This article is about using the new TipKit framework for iOS 17 to help the user discover various features within your app.

This article is about using the new TipKit framework for iOS 17 to help the user discover various features within your app.

This article will cover:

  • displaying an inline tip
  • displaying floating tip
  • display tip based on conditions
  • display tip based on a counter

This article is primarily for SwiftUI.

💡
This article is written with public resources and may contain screenshots from public Apple documentations and example codes. To see how it works, please try it yourself with the Xcode 15.

Define a Tip object

A Tip object contains the image, title, description, and the actions for a tip. Here is a simple tip:

import TipKit

struct HashTagPostButtonTip: Tip {
    var image: Image? {
        Image(systemName: "star.bubble")
    }
    var title: Text {
        Text("Send a Quick Response")
    }
    var message: Text? {
        Text("Double-tap a message, then choose a Tapback, like a ♥︎.")
    }
}

In the var asset: Image?, you can provide an optional image to be displayed on the left side of the tip.

In the var title: Text, you will provide a SwiftUI Text for the text you want to show as the title of this tip.

In the var message: Text?, you can provide an optional description string that will be displayed at the bottom of the title.

There is also a var rules: [Rule] and the var actions: [Action] parameter, which we will talk about in the next section of this article.

Displaying a tip

There are 2 ways to display a tip.

inline tips

You can display the tip within your view. The tip view will come with an arrow that points to your feature (for example, a button). You can do that by directly including TipView in your view code:

import SwiftUI
import TipKit

struct TipWithArrow: View {
    var body: some View {
        VStack {
            
            HStack {
                TipView(HashTagPostButtonTip(), arrowEdge: .trailing)
                
                Image(systemName: "number")
                    .font(.system(size: 30))
                    .foregroundStyle(.white)
                    .padding()
                    .background { Circle().foregroundStyle(.blue) }
            }
            
        }
        .padding()
        .task {
            try? Tips.configure([
                .displayFrequency(.immediate),
                .datastoreLocation(.applicationDefault)
            ])
        }
    }
}

#Preview {
    TipWithArrow()
}

You can use the arrowEdge parameter to decide which direction the arrow points to. Set to .trailing so that the tip will display an arrow point to the right side (meaning the feature button is on the right); or set it to .leading when the feature button is on the left (and the shown tip is on the right side of the button).

Floating (popover) tips

You can also use a view modifier to attach a floating tip to a button. This is useful for example when you want to show a tip for a navigation button:

struct PopOverTip: View {
    
    var hashtagButtonTip = HashTagPostButtonTip()
    
    var body: some View {
        VStack {
            
            Text("Hello world!")
            
        }
        .padding()
        .toolbar {
            ToolbarItem(placement: .navigationBarTrailing) {
                Image(systemName: "number")
                    .onTapGesture {
                        hashtagButtonTip.invalidate(reason: .actionPerformed)
                    }
                    .popoverTip(hashtagButtonTip)
            }
        }
        .task {
            try? Tips.configure([
                .displayFrequency(.immediate),
                .datastoreLocation(.applicationDefault)
            ])
        }
    }
    
}

#Preview {
    PopOverTip()
}

Tip frequency

By default, each tip only shows once.

When you set .displayFrequency(.immediate), the tip will be immediately shown if the user has not seen the tip before.

You can also set other display frequencies. For example, if you set it to .hourly, the system makes sure that no more than 1 tip is shown every hour.

You can also customize where the tip data (whether the tips have been shown or not) by using the .datastoreLocation(.applicationDefault) function.

Show tip when certain condition is met

You can only show a tip when certain condition is met. For example, a tip for a premium feature should only be shown if the user has purchased the premium feature.

You can define a static variable for the Tip object. Then, you can assign values to that static variable to control whether the tip is shown or not.

import TipKit

struct PremiumUserOnlyTip: Tip {
    
    @Parameter
    static var isPremiumUser: Bool = false
    
    var image: Image? {
        Image(systemName: "wand.and.rays")
    }
    var title: Text {
        Text("Add an Effect")
    }
    var message: Text? {
        Text("Choose a fun Live Photo effect like Loop or Bounce.")
    }
    
    var rules: [Rule] {
        #Rule(Self.$isPremiumUser) {
            $0 == true
        }
    }
    
}

On app launch, we can set the static variable isPremiumUser so the tip will only be shown for users who is premium.

PremiumUserOnlyTip.isPremiumUser = isPremiumUser

Record user interactions and show tips accordingly

You can record user interactions as a counter (like the number of times that the user used your app) and only show tips when the user has met a requirement.

import TipKit

struct UsageFrequencyBasedTip: Tip {
    
    static let numerOfTimesOpened: Event = Event(id: "com.example.TipKit.numberOfTimesOpened")
    
    var image: Image? {
        Image(systemName: "star.fill")
    }
    var title: Text {
        Text("Tap to download HD picture")
    }
    var message: Text? {
        Text("Only for premium users")
    }
    
    var rules: [Rule] {
        #Rule(Self.numerOfTimesOpened) {
            $0.donations.count >= 3
        }
    }
    
}

In the above code, we record the number of times user opened the app using an Event object. In the rules parameter, we use a Swift Macro and indicate that the tip will only be shown if the times user opened is larger than or equal to 3.

Now, we can increment the counter by using the following code in our app:

Button("tap this button 3 times") {
    Task {
        await UsageFrequencyBasedTip.numerOfTimesOpened.donate()
    }
}

When you call donate() function for 3 times, the tip will be shown.

Tip with custom actions

You can also provide your action buttons for a tip:

import TipKit

struct TipWithOptions: Tip {
    
    var image: Image? {
        Image(systemName: "star.bubble")
    }
    
    var title: Text {
        Text("Send a Quick Response")
    }
    
    var message: Text? {
        Text("Double-tap a message, then choose a Tapback, like a ♥︎.")
    }
    
    var actions: [Action] {
        return [
            .init(id: "learn-more", title: "Learn more", perform: {
                print("Learn more tapped")
            }),
            .init(id: "enable-feature", title: "Enable magic feature", perform: {
                print("Enable feature tapped")
            })
        ]
    }
    
}

Debugging tips

You can debug your tips by using the following function:

Reset the storage database for whether tips are shown or not:

try? Tips.resetDatastore()

The above code must be called before calling Tips/configure(options:)

You can also show all tips regardless of whether a tip has been shown before:

Tips.showAllTipsForTesting()

You can show a specific tip regardless of whether it has been shown before:

Tips.showTipsForTesting([ExampleTip.self])

in the parameter, provide an array of the type of the tips.

You can also hide all tips, or just hide a specific type of tip:

Tips.hideAllTipsForTesting()
Tips.hideTipsForTesting(_ tips: [Tip.Type])

Good vs Not Good

You should not show promotion tips, do not use TipKit to show error messages, only show tip that is associated with an actionable item (for example, a clickable feature button), and do not show detailed steps within the tip (like shown in the example of the NG)

Sample code

I will upload the sample Xcode project after iOS 17 is publicly available.