How to use TipKit to highlight features in your iOS app

Enhancing user experience: add tips to improve discoverability of new functionalities

Bruno Lorenzo
5 min readOct 3, 2023

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.

  1. Use @Parameter wrapper to wrap the property that we want to track.
  2. 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.

  1. We must define an Event instance with a custom ID to identify the user interaction that we want to track.
  2. 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:

  1. Will the Tip teach users about a new feature?
  2. Will the Tip help users to discover a hidden feature?
  3. 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.

Have any questions? Feel free to drop me a message! 🙂

  • 🤓 Join me on Twitter for regular content on iOS development tips and insights
  • 🚀 Check out my GitHub where I share all my example projects

--

--

Bruno Lorenzo

Software Engineer | Innovation Manager at www.houlak.com | Former iOS Tech Lead | I write about iOS, tech, and producitivy