š Discover Appleās New Framework Called āTipKitā
Introduction
In WWDC23, Apple introduced lots of new things to help us to make more efficient, maintainable and be able to avoid third-party libraries frameworks. āTipKitā is the one of the framework that allows us to provide highlight the new features of our app among these.
Pros š„³
- Both SwiftUI and UIKit implementation available
- Compatible with whole Apple platforms including iOS, macOS, iPadOS, visionOS
- Can be easily integrate, testable
- Chance to avoid third-party libraries such as
ephread/Instructions
Cons š¤
- Only compatible with iOS17 and newer versions as always ( Apple did this again without thinking the developers šŖ )
- Could irritate the users whose using your app if you wouldnāt act correctly in the app while highlighting your new features
šØ Before we jumped out, please note that this feature only works with Xcode Beta 15ā5.0 and newer versions. Here is the link that you can download the latest Xcode version below. ā¬ļø
Letās dive in to explore more details about āTipKitā
Letās start with developing the screen that has an image to be to work on
struct ContentView: View {
var body: some View {
VStack {
Button {
//
} label: {
Image(.profile)
.resizable()
.frame(width: 200, height: 200)
.clipShape(.circle)
.padding(.leading, 22)
}
}
.padding()
.preferredColorScheme(.dark)
}
}
The first scenario is when we launched the app we expect that tooltip appear with title, message, icon and button actions on the bottom of the image. To be able to do this we have to create a struct class that conformed by Tip protocol. This protocol consist of ā¬ļø
id: String // Unique Identifier that auto assigned
title: Text // The text that shows on the top of TipView as Title
message: Text? // The text that shows on the middle of TipView as Message
asset: Image? // The image that shows on the left side of TipView as icon
actions: [TipKit.Action] // The action buttons that you can trigger user in the TipView
rules: [TipKit.Rule] // Rules for adjusting to appearing of TipView
options: [TipOption] // Customization options for style of TipView
Here is the UserWelcomeTip struct
struct UserWelcomeTip: Tip {
var title: Text {
Text("Welcome to The App")
}
var message: Text? {
Text("Enjoy it, discover and fun !")
}
var asset: Image? {
Image(systemName: "info.circle.fill")
}
var actions: [Action] {
[
Tip.Action(
id: "learn-about-me",
title: "Learn About Me"
),
]
}
}
Create an instance of UserWelcomeTip struct in the ContentView, then we will add popoverTip to the related button and set the property which name is userWelcomeTip. In addition to present the tip we have to adjust some settings with TipsConfiguration when view has appeared.
private let userWelcomeTip = UserWelcomeTip()
Button {
// action
} label: {
Image(.profile)
.resizable()
.frame(width: 100, height: 100)
.clipShape(.circle)
.padding(.leading, 22)
}
.popoverTip(userWelcomeTip, arrowEdge: .bottom)
.task {
try? await Tips.configure() {
DisplayFrequency(.immediate)
DatastoreLocation(.applicationDefault)
}
}
You can add custom configurations into to the configure() closure that conforms by TipsConfiguration which is currently we got two options including DisplayFrequency and DatastoreLocation.
DisplayFrequency helps you to adjust your tips frequency which consist of immediate, hourly, daily, weekly, monthly. We established that frequency using UserDefaults before. How cool and very simple it is, right?
DatastoreLocation allows you to configure the location that you want to store the tips preferences whatever path url inside the phone you want. In addition to this it has shouldReset parameter to reset the preferences as beginning. Itās default value is false, you can use this parameter depends on your needs.
public init(_ location: DatastoreLocation, shouldReset: Bool = false)
Run the application and you will be able to show your first tip that you just created on the bottom of the profile image as below š„³
What if we want to present tips depends on some condition ?
Some scenarios, you may want to present your tips by conditionally. In this particular case Apple brings up with @Parameter and #Rule macros to allows us to manage conditions by each tip.
šØ Before we use these macros you have to implement this into the BuildSettings -> Other Swift Flags section otherwise Xcode will not recognize these macros because itās still experimental
-external-plugin-path
$(SYSTEM_DEVELOPER_DIR)/Platforms/iPhoneOS.platform/Developer/usr/lib/swift/host/plugins#$(SYSTEM_DEVELOPER_DIR)/Platforms/iPhoneOS.platform/Developer/usr/bin/swift-plugin-server
Letās add the rule condition the our existing tip called UserWelcomeTip
Imagine you want to present the tip when user click the profile image, to be able to do that we need a parameter that we used in the rules of tip structs and we need to define our rule inside of the struct.
Here is the modified version of UserWelcomeTip Struct file below
struct UserWelcomeTip: Tip {
@Parameter
static var isActive: Bool = false
var title: Text {
Text("Welcome to The App")
}
var message: Text? {
Text("Enjoy it, discover and fun !")
}
var asset: Image? {
Image(systemName: "info.circle.fill")
}
var actions: [Action] {
[
Tip.Action(
id: "learn-about-me",
title: "Learn About Me"
),
]
}
var rules: [Rule] = [
#Rule(Self.$isActive) { $0 == true }
]
}
So, this tip only trigger when the static isActive property turns into the true. Until then, we will not encountered this tip in the application. Just set true to the isActive property when the button that has profile Image clicked to present the tip.
Button {
UserWelcomeTip.isActive = true
} label: {
Image(.profile)
.resizable()
.frame(width: 100, height: 100)
.clipShape(.circle)
.padding(.leading, 22)
}
.popoverTip(userWelcomeTip)
.task {
try? await Tips.configure() {
DisplayFrequency(.immediate)
DatastoreLocation(.applicationDefault, shouldReset: true)
}
}
š Letās dive into what happens in the background of these macros
Apple introduced SwiftMacros in WWDC23, basically it allows us to generate codes instead of us and help us to reduce our codebase more effective. Here is the link that you can get more knowledge about SwiftMacros from Apple Documentation below ā¬ļø
https://developer.apple.com/documentation/Swift/applying-macros#
When you select @Parameter or #Rule macros and right click you will see that there is an ExpandMacro selection in the panel, you will be able to see what code written from macro for you.
Itās awesome isnāt it ? Donāt be late to try SwiftMacros for your own projects in the future š§āšØ
Furthermore, letās make our rules get more complex. Now weāre changing our scenario to user click count on the image to be able to present the tip.
Add an event to track our user click count into the UserWelcomeTip struct to be able to configure our rules depends on this event therefore we will have a chance to add click count condition rule depends on the user click count to present our tip
struct UserWelcomeTip: Tip {
@Parameter
static var isActive: Bool = false
// we define an event to capture user click count
static let numberOfTimesShowedOption: Event = Event(id: "dev.alkincakiralar.tip.numberOfTimesShowedOption")
var title: Text {
Text("Welcome to The App")
}
var message: Text? {
Text("Enjoy it, discover and fun !")
}
var asset: Image? {
Image(systemName: "info.circle.fill")
}
var actions: [Action] {
[
Tip.Action(
id: "learn-about-me",
title: "Learn About Me"
),
]
}
var rules: [Rule] = [
#Rule(Self.$isActive) { $0 == true },
// depends on the relevant capture event we add one more rule and
// we're checking the click count is equal and larger than 3
#Rule(Self.numberOfTimesShowedOption) { $0.donations.count >= 3 }
]
}
To be able to trigger that event we have to do call donate function from numberOfTimesShowedOption property wherever we want, it will kept in the storage that we defined in the DatastoreLocation. In this way, we can avoid to store these kind of rules in UserDefaults.
Button {
// we called the donate function every click of this button.
// when user clicked 3 times to that button the tips that we adjusted
// will be present immediately
UserWelcomeTip.numberOfTimesShowedOption.donate()
UserWelcomeTip.isActive = true
} label: {
Image(.profile)
.resizable()
.frame(width: 100, height: 100)
.clipShape(.circle)
.padding(.leading, 22)
}
.popoverTip(userWelcomeTip)
.task {
try? await Tips.configure {
DisplayFrequency(.immediate)
DatastoreLocation(.applicationDefault, shouldReset: true)
}
}
What about testing process ?
Apple gives us an opportunity to be able to test the tips scenarios in the app while making the UITests.
In the following methods will help you to test your tips easily.
- Tips.showAllTips()
- Tips.showTips([userWelcomeTip]
- Tips.hideTips([userWelcomeTip]
- Tips.hideAllTips()
Conclusion
This framework is a great chance to introduce our new features in the app. However, we should be aware of itād be consequences when we use it more than itās purpose. We should be avoid using this framework unnecessary times. Apple gives us some recommendations that when we should use use this inside the app. Here are the recommendations and not recommendations images from WWDC23 about TipKit.
References
- WWDC23 Session: https://lnkd.in/dDFtHqkG
- TipKit Documentation: https://lnkd.in/dJBbG6zz