TipKit for iOS — Exploring the Basics (Part 1)
TipKit is a new framework unveiled by Apple in the 2023 edition of the World Wide Developer Conference (WWDC23). The goal with this framework is to give iOS Engineers and Designers one more tool to offer help to their customers and to teach people about new or less obvious features in an application (feature discoverability). At the moment, TipKit is available on iPhone, iPad, Mac, Apple Watch, Apple TV and VisionOS.
Although, there is a lot of potential in Tips, Apple advises that we should be careful not to overwhelm users. To make an effective use of Tips, they should be used sparingly and their main goal should be to guide users to discover non-obvious or new features that they haven't discovered on their own. Tips should be actionable, instructional and easy to remember. They should not become a distraction or appear unnecessarily.
iOS teams should not use Tips to guide people through their app or for advertising and promotion purposes.
Configuration Set up
Before creating a new Tip we need to create a configuration in the app entry point. This sets up Tips in the app and their associated events which should persist between app launches. To make tips easy to test we should include the displayFrequency option.
init() {
try? Tips.configure([.displayFrequency(.immediate)])
}
Create a new Tip
To create a Tip we create a new struct
that conforms to the Tip
protocol. This type sets the content of the Tip, as well as, the conditions for when it is displayed.
import Foundation
import TipKit
struct MyTip: Tip {
var title: Text {
Text("Inline Tip")
}
var message: Text? {
Text("Inline Tips should should be used whenever possible to avoid covering UI elements")
}
var image: Image? {
Image(systemName: "star")
}
var actions: [Action] {
Action(id: Action.Control.learnMore.id, title: Action.Control.learnMore.title)
}
}
extension Tip.Action {
enum Control {
case learnMore
var id: String {
switch self {
case .learnMore:
return "learn-more"
}
}
var title: String {
switch self {
case .learnMore:
return "Learn More"
}
}
}
}
Here we set the title, message, image and possible actions the Tip can have. The title should tell people what the feature is. On the other hand, the message should contain easy to remember instructions that explain why users should use the feature to accomplish a certain task. As far as images are concerned, these should be included in Tips if people associate it with a certain feature. For example, if our Tip relates to favouriting an item, then it could be a good idea to include a star in the Tip.
Finally, actions display controls associated with the tip itself. These are customisable buttons that the user can tap to be taken to other places in the app, settings or other apps. In our example bellow, our Tips will take the user to a web page in Safari.
Inline Tips
Inline Tips should be used whenever possible. This way we avoid covering UI elements that people may want to interact with. Inline Tips adjust the application’s user interface to temporarily fit around the Tip and accommodate for its appearance and dismissal.
To create an Inline Tip we add a TipView
to our code above the list because that is where we want to place it. And because our Tip includes an action we add a closure so that we can add extra logic to do something when the action is triggered.
import Foundation
import TipKit
struct InlineTip: View {
@Environment(\.openURL) var openURL
private var tip = MyTip()
typealias Control = Tip.Action.Control
var body: some View {
NavigationStack {
VStack {
TipView(tip, arrowEdge: .bottom) { action in
switch action.id {
case Control.learnMore.id:
openURL(URL(string: "https://developer.apple.com/documentation/")!)
default:
break
}
}
.padding()
List {
ForEach(1 ... 10, id: \.self) {
Text("Item \($0)")
}
}
.listStyle(.plain)
}
.navigationTitle(Text("Custom View"))
}
}
}
The arrowEdge
argument is optional and allows us to specify which edge of the Tip displays the arrow.
Popover Tips
This type of Tip should be used if we don't want to adjust the underlying layout or if we don't need to interact with any of the controls that are obscured by the Tip.
Apple advises iOS Teams not to use images in Popover Tips as the Tip already points to the feature being highlighted.
import Foundation
import TipKit
struct MyPopoverTip: Tip {
var title: Text {
Text("Sorting now available")
}
var message: Text? {
Text("Sort the items in the list by dragging them to the desired position.")
}
}
For this example, we will add a Tip to a view in the toolbar indicating that the user can sort items in the list. We can also omit the arrowEdge
parameter because SwiftUI is smart enough to understand what is the best placement for the tip and adjust the arrowEdge
accordingly.
import SwiftUI
import TipKit
struct PopoverTip: View {
private var tip = MyPopoverTip()
var body: some View {
NavigationStack {
VStack {
List {
ForEach(1 ... 10, id: \.self) {
Text("Item \($0)")
}
}
.listStyle(.plain)
}
.navigationTitle(Text("Custom View"))
.toolbar {
ToolbarItem(placement: .topBarTrailing) {
Image(systemName: "arrow.up.arrow.down")
.popoverTip(tip)
.onTapGesture {
tip.invalidate(reason: .actionPerformed)
}
}
}
}
}
}
In this example, we make use of the invalidate
function for Tips that allows us to specify the how / when we want the Tip to be dismissed. In this case, when the user taps the screen the Tip is automatically dismissed.
In the next article about Tips we will go beyond the basics and explore other topics, such as, persistence, parameters and display frequency.
You can go beyond the basics of TipKit and find Part 2 of this article here:
https://medium.com/@tomas-sant-mamede/tipkit-going-beyond-the-basics-part-2-1d77b03a0e09
Useful Links:
Apple Documentation I, Apple Documentation II, WWDC23 Video on TipKit, Apple Design Patterns for Tips