TipKit for iOS — Going beyond the Basics (Part 2)
In the last article on TipKit we went through the basics of this new framework that Apple introduced last year at WWDC. We learned how to set up a new Tip with title, message, image and actions and how to integrate it with the UI of an app. In the code examples, we learned how to present an Inline Tip, which is embedded in the view with the feature being highlighted, and a Popover Tip, which is displayed on top of the current screen UI, while obscuring the elements below.
In this article, we will dive further into more advanced features of TipKit that will enable you to do more interesting and complex things with this framework. We will cover Persistency, Eligibility Rules (parameter or event based) and Display behaviours.
Persistency
In order to enable TipKit in an application we must call the configure
method in app’s entry point. This method receives an array of ConfigurationOptions
that allows us to customize the datastore location for our tips and the default frequency in which they show up in the user interface.
By default Tips are stored in applicationDefault
which maps to the Application Support
directory. In fact, if we dig a little bit deeper in Finder, inside the hidden Library
folder, we will discover that inside our app’s folder, that can be found inside our simulator’s folder, there is another hidden folder under Application Support
named .tipkit
. Inside this folder there are several .db
files were the content and settings of our Tips are stored.
In my case the path is: /Users/tomasmamede/Library/Developer/CoreSimulator/Devices/6E24ED18-B54C-4D80–86C2-BEB3A807E42E/data/Containers/Data/Application/2A95406B-11C6–4FDA-B80A-BA0FAF949B03/Library/Application Support
Using the DB Browser for SQLite application for macOS we find the familiar SQLite files created by CoreData.
This investigation shows that by calling the configure
method the persistence of the Eligibility Rules, Options and Behaviours is handled automatically by the TipKit framework.
Eligibility Rules
In the last article on TipKit we went through some design and user experience best practices concerning Tips and how to use them the right way. Throughout the documentation, Apple hints several times to the fact that Tips should only highlight features that are of interest to the person and never come off as spammy or irrelevant. They should always be educational and add value to the way the user interacts with the application.
That being said, Tips may not be relevant to everyone, specially users that have already discovered the feature we are trying to highlight. With Eligibility Rules provided by TipKit we can determine exactly when a Tip is displayed to make sure it appears at the ideal time and for the right reasons. These Eligibility rules can be used alone or together.
There are two main types of rules: parameter-based rules and event-based rules.
Parameter-based rules are persistent and are mostly used to show Tips based on a value type we want to write an expression around. On the other hand, event-based rules allow us to define an action that needs to be performed before the person using the app becomes eligible to see the Tip.
Parameter-Based Rules
To show a parameter based rule we need to add a new parameter to our Tip struct
. TipKit introduces the new Parameter
property wrapper that monitors the state of the wrapped value to reevaluate any dependent Tip rules when the value of the property changes.
To exemplify this, I am going to create a simple view with a button displaying the text Tap Me and in my Tip I will create a new parameter-based rule that will keep track of the value of the property buttonWasTapped
. When this button is tapped and the buttonWasTapped
property changes to true
the user will see the Tip.
import SwiftUI
import TipKit
struct PopoverTip: View {
private var tip = MyPopoverTip()
var body: some View {
NavigationStack {
VStack {
Button("Tap Me") {
MyPopoverTip.buttonWasTapped.toggle()
}
}
.navigationTitle(Text("Custom View"))
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Image(systemName: "arrow.up.arrow.down")
.popoverTip(tip)
.onTapGesture {
// Dismiss Tip
tip.invalidate(reason: .actionPerformed)
}
}
}
}
}
}
In order for this to work, we must add the rules
property to our struct
that conforms to Tip
and create the new property we want to track wrapped with the @Parameter
property wrapper.
import Foundation
import TipKit
struct MyPopoverTip: Tip {
@Parameter static var buttonWasTapped: Bool = false
var title: Text {
Text("Sorting now availale")
}
var message: Text? {
Text("Sort the items in the list by dragging them to the desired position.")
}
var rules: [Rule] {
#Rule(Self.$buttonWasTapped) { $0 == true }
}
}
Rules can be as general or as specific as needed and can be combined to target the ideal audience at the proper time.
Event-based Rules
Event-based rules are used to track and display tips based on user actions. and they can work as a standalone type of rule ou combined with Parameter-based rules that allow iOS Teams to define more complex rules.
In our example, we are going to define a new Event-based rule to keep track of the number of times a new sheet view is displayed. This rule will work alongside the Parameter-based rule we defined earlier. Once this new view appears twice and the Parameter-based rule is also satisfied, our Tip will become eligible to be displayed.
First, we must create a new simple SheetView
.
import SwiftUI
struct SheetView: View {
@Environment(\.dismiss) var dismiss
var body: some View {
NavigationStack {
Button("Dismiss") {
dismiss()
}
.navigationTitle("Sheet View")
}
}
}
Also, we have to define a new event property in our Tip struct
. An Event
in TipKit is a new type that is used to define a repeatable user defined action.
struct MyPopoverTip: Tip {
@Parameter static var buttonWasTapped: Bool = false
static let enteredSheetView: Event = Event(id: "present-sheet-view")
var title: Text {
Text("Sorting now availale")
}
var message: Text? {
Text("Sort the items in the list by dragging them to the desired position.")
}
var rules: [Rule] {
#Rule(Self.$buttonWasTapped) { $0 == true }
#Rule(Self.enteredSheetView) { $0.donations.count >= 2}
}
}
Once these are in place, we must update our main view to show the new sheet once the new PresentSheet
button is tapped.
struct PopoverTip: View {
private var tip = MyPopoverTip()
@State private var presentSheet: Bool = false
var body: some View {
NavigationStack {
VStack(spacing: 10) {
Button("Tap Me") {
MyPopoverTip.buttonWasTapped.toggle()
}
Button("Present Sheet") {
presentSheet.toggle()
}
}
.navigationTitle(Text("Custom View"))
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Image(systemName: "arrow.up.arrow.down")
.popoverTip(tip)
.onTapGesture {
tip.invalidate(reason: .actionPerformed)
}
}
}
.sheet(isPresented: $presentSheet) {
SheetView()
}
}
}
}
Finally, in our sheet view, once it is displayed, we have to donate to the event. This is accomplish by using an async
Task
inside the .onAppear
view modifier.
.onAppear {
Task {
await MyPopoverTip.enteredSheetView.donate()
}
}
Display Frequency
Another property that comes with TipKit and that can help us refine the way we use Tips inside our apps is Options
. This new type represents customisations that we can apply to the behaviour of a Tip and can help us set the frequency with which a Tip shows up on screen.
For example, if we want to display a Tip only once we just need to add this segment of code to our Tip struct
.
var options: [Option] {
MaxDisplayCount(1)
}
We can also use IgnoresDisplayFrequency
to control whether a Tip obeys the preconfigured display frequency interval.
Display Behaviours
TipKit provides display and dismissal features so that Tips appear at a good cadence and stay on screen only as long as their are relevant.
In the entry point of our app, when setting up a TipKit configuration we can define the frequency with which Tips show up in our app. We can use the values .immediate
if we don't want to impose any limitations on frequency and want to show all Tips as soon as they are eligible to appear or .hourly
if we don’t want to show more than one Tip per hour. We can also use the values .daily
, .weekly
or .monthly
.
TipKit can also sync tip status via iCloud to ensure that Tips seen on one device won’t be seen on the other.
Useful Links: WWDC video on TipKit, Apple Documentation
Learn more about Enums in Swift with this deep dive below.
https://medium.com/@tomas-sant-mamede/enums-in-swift-a-deep-dive-0cb75d0e2880