How to use TipKit to highlight features in your iOS app
Enhancing user experience: add tips to improve discoverability of new functionalities
Releasing new features to our app is always exciting.
We think it will improve our user’s experience. Unfortunately, there may be times when our users don’t see the benefit of the new functionalities that we release, or don’t even get to know they exist in the first place. To avoid this issue, and help us to make our new features more discoverable, Apple has introduced a new framework: TipKit.
Tips are meant to be educational, helping us guide the users to our app’s most important details.
So, if you plan on adding new features to your app, let’s look at how you can make them more discoverable with tips.
We will be creating a Tip for a new functionality that we added to our Coffee Shop App. Now, users can view their order history and quickly access any of them to repeat it.
If you're interested in the app, you can check the source code here
Tip Content
The very first thing we need to do is define the content we want to show in the tip by implementing Tip
protocol.
struct HistoryTip: Tip {
var title: Text {
Text("Access your order's history!")
}
var message: Text? {
Text("You can quickly repeat any order you want")
}
}
We can also include an Image for the tip/
Loading tips
Before we can display any tips, we must load them into the app. We do this by calling configure(_:)
method when our app startup.
@main
struct CoffeeShopDemoApp: App {
init() {
try? Tips.configure()
}
var body: some Scene {
WindowGroup {
ContainerView()
}
}
}
Showing tips
We can choose between two presentations styles.
Inline
It's an inline component that we can put in any place we want. We just need to provide a tip instance and the direction we want to put the arrow.
import SwiftUI
import TipKit
struct HomeView: View {
private let tip = HistoryTip()
var body: some View {
HStack {
TipView(tip, arrowEdge: .trailing)
Button {...}
}
.padding(.horizontal, 16)
}
}
We should aim to place theTipView
component near the feature we want to highlight.
Popover
It's a view modifier that presents the tip above all the screen's elements.
import SwiftUI
import TipKit
struct HomeView: View {
private let tip = HistoryTip()
var body: some View {
HStack {
Text("Menu")
.font(.largeTitle)
Spacer()
Button {
...
} label : {
Image(systemName: "clock.arrow.circlepath")
.resizable()
.aspectRatio(contentMode: .fit)
.frame(width: 25, height: 25)
}
.popoverTip(tip)
.onTapGesture {
tip.invalidate(reason: .actionPerformed)
}
}
.padding(.horizontal, 16)
}
}
When we display a tip and users interact with it, either because they closed it or accessed the feature, we should tell the system that the tip doesn't need to show anymore.
We do this by using invalidate(reason:)
function.
Control when to show the tip
This is pretty good and straightforward already. However, with this implementation, we will be showing the tip every time the user enters the screen or even when the user already discovered the feature and used it.
We don't want to overwhelm our users, so how do we control when to show the tip? Using rules.
Rules are part of the Tip
protocol that allow us to evaluate conditions using the #Rule
macro.
Parameters Rules
Rules that track our app's state.
For example, in our case, we might want to make the tip eligible only if the user didn't discover the new feature by themselves.
- Use
@Parameter
wrapper to wrap the property that we want to track. - Then, in the new feature’s view, we change the property value when users enter the view:
HistoryTip.alreadyDiscovered = true
.
struct HistoryTip: Tip {
/// Parameters-Rules
@Parameter
static var alreadyDiscovered: Bool = false
var title: Text {
Text("Access your order's history!")
}
var message: Text? {
Text("You can quickly repeat any order you want")
}
var rules: [Rule] {
[
#Rule(Self.$alreadyDiscovered) { $0 == false }
]
}
}
Event Rules
Event rules, on the other hand, track user interactions.
In our case, let's assume that we want to show the tip only when users have placed at least two orders.
- We must define an
Event
instance with a custom ID to identify the user interaction that we want to track. - Whenever users place an order, we donate to that event:
HistoryTip.orderPlaced.donate()
.
struct HistoryTip: Tip {
/// Parameters-Rules
@Parameter
static var alreadyDiscovered: Bool = false
/// Event-Rules
static let orderPlaced = Event(id: "order-placed")
var title: Text {
Text("Access your order's history!")
}
var message: Text? {
Text("You can quickly repeat any order you want")
}
var rules: [Rule] {
[
#Rule(Self.$alreadyDiscovered) { $0 == false },
#Rule(Self.orderPlaced) { $0.donations.count > 1 }
]
}
}
How do we test tips?
Apple provides us with a couple of utility functions that we can use to override the Tip configuration when we're in development phase.
Tips.showAlltipsForTesting()
Show all defined tips.Tips.showTipsForTesting([tip1, tip2, tip3])
Show specific tips.Tips.hideAllTipsForTesting()
Hide all tips defined.Tips.resetDataStore()
Reset all TipKit data and define tip's state.
These methods must be called before
Tips.configure()
Takeaways
Remember, tips are meant to be educational, so if we want to show a Tip to our users, we should ask these 3 questions first:
- Will the Tip teach users about a new feature?
- Will the Tip help users to discover a hidden feature?
- Will the Tip show users how to accomplish a specific task in a faster way?
If the answer to any of the above questions is yes, then the Tip is a good fit.