Dunfey · Hotel WWDC as data, est. 1983
Front desk everything
Years
Topics

2023 DesignEssentialsApp Services

WWDC23 · 15 min · Design / Essentials / App Services

Make features discoverable with TipKit

Teach people how to use your app with TipKit! Learn how you can create effective educational moments through tips. We’ll share how you can build eligibility rules to reach the ideal audience, control tip frequency, and strategies for testing to ensure successful interactions.

Watch at developer.apple.com ↗

Transcript all transcripts

Chapters

Code shown on screen · 15 snippets

Create a tip swift · at 1:55 ↗
struct FavoriteBackyardTip: Tip {

    var title: Text {
        Text("Save as a Favorite")
    }
    
    var message: Text {
        Text("Your favorite backyards always appear at the top of the list.")
    }
}
Configure TipsCenter swift · at 2:48 ↗
@main
struct BackyardBirdsApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }

    // ...

    init() {
        TipsCenter.shared.configure()
    }
}
Add actions and an asset to a tip swift · at 3:18 ↗
struct FavoriteBackyardTip: Tip {

    var title: Text {
        Text("Save as a Favorite").foregroundColor(.indigo)
    }
    
    var message: Text {
        Text("Your favorite backyards always appear at the top of the list.")
    }
    
    var asset: Image {
        Image(systemName: "star")
    }
    
    var actions: [Action] {
        [
            Tip.Action(
                id: "learn-more", 
                title: "Learn More"
            )
        ]
    }
}
Create a popover view swift · at 4:53 ↗
private let favoriteBackyardTip = FavoriteBackyardTip()

// ...

.toolbar {
    ToolbarItem {
        Button {
            backyard.isFavorite.toggle()
        } label: {
            Label("Favorite", systemImage: "star")
                .symbolVariant(
                    backyard.isFavorite ? .fill : .none
                )
        }
        .popoverMiniTip(tip: favoriteBackyardTip)
    }
}
Add a parameter based rule swift · at 6:38 ↗
struct FavoriteBackyardTip: Tip {

    @Parameter
    static var isLoggedIn: Bool = false

    // ...

    var rules: Predicate<RuleInput...> {

        // User is logged in
        #Rule(Self.$isLoggedIn) { $0 == true }
    }
}
Add an event based rule swift · at 7:16 ↗
struct FavoriteBackyardTip: Tip {

    @Parameter
    static var isLoggedIn: Bool = false
  
    static let enteredBackyardDetailView: Event = Event<DetailViewDonation>(
        id: "entered-backyard-detail-view"
    )

    // ...
    
    var rules: Predicate<RuleInput...> {
        // User is logged in
        #Rule(Self.$isLoggedIn) { $0 == true }
            
        // User has entered any backyard detail view at least 3 times
        #Rule(Self.enteredBackyardDetailView) { $0.count >= 3 }
    }
}
Donate the event when a view appears swift · at 7:34 ↗
.onAppear {
     FavoriteBackyardTip.enteredBackyardDetailView.donate()
}
Filter event donations in an event based rule swift · at 7:59 ↗
// User has entered any backyard detail view at least 3 times in the past 5 days
#Rule(Self.enteredBackyardDetailView) { 
    $0.donations.filter { 
        $0.date > Date.now.addingTimeInterval(-5 * 60 * 60 * 24)
    }
    .count >= 3
}
Create a custom donation swift · at 8:34 ↗
// Create the associated type
extension BackyardDetailTip {
    struct DetailViewDonation: DonationValue {
        let backyardID: Int
    }
}

// Donate the unique id of the backyard detail being viewed
.onAppear {
     BackyardFavoriteTip.enteredBackyardDetailView.donate(
         with: .init(backyardID: backyard.id)
     )
}


struct FavoriteBackyardTip: Tip {

    // ...

    var rules: Predicate<RuleInput...> {
        // Update the rule to specify a backyardID
        #Rule(Self.enteredBackyardDetailView) {
            $0.donations.filter {
                $0.date > Date.now.addingTimeInterval(-5 * 60 * 60 * 24) 
            }
            .largestSubset(by: \.backyardID)
            .count >= 3
        }
    }
}
Configure display frequency swift · at 9:57 ↗
// One tip per day.
TipsCenter.shared.configure {
    DisplayFrequency(.daily)
}

// One tip per hour.
TipsCenter.shared.configure {
    DisplayFrequency(.hourly)
}

// Custom configuration. Only show one tip every five days.
let fiveDays: TimeInterval = 5 * 24 * 60 * 60
TipsCenter.shared.configure {
    DisplayFrequency(fiveDays)
}

// No frequency control. Show all tips as soon as eligible.
TipsCenter.shared.configure {
    DisplayFrequency(.immediate)
}
Turn off display frequency controls for a tip swift · at 10:34 ↗
struct FavoriteBackyardTip: Tip {

    // ...
  
    var options: [Option] {
        [.ignoresDisplayFrequency(true)]
    }
}
Invalidate a tip swift · at 11:27 ↗
Button {
    backyard.isFavorite.toggle()

    // When user taps the favorite button, dismiss the tip
    favoriteBackyardTip.invalidate(reason: .userPerformedAction)
} label: {
    Label("Favorite", systemImage: "star")
        .symbolVariant(backyard.isFavorite ? .fill : .none)
}
.popoverMiniTip(tip: favoriteBackyardTip)
Configure max display count on a tip swift · at 11:41 ↗
struct FavoriteBackyardTip: Tip {

    // ...

    var options: [Option] {
        [.maxDisplayCount(5)]
    }
}
Programmatically call testing API swift · at 12:46 ↗
// Show all defined tips in the app
TipsCenter.showAllTips()

// Show some tips, but not all
TipsCenter.showTips([tip1, tip2, tip3])

// Hide some tips, but not all
TipsCenter.hideTips([tip1, tip2, tip3])

// Hide all tips defined in the app
TipsCenter.hideAllTips()

// Purge all TipKit related data
TipsCenter.resetDatastore()
Configure launch arguments in your scheme swift · at 13:31 ↗
// Show all defined tips in the app
com.apple.TipKit.ShowAllTips 1

// Show some tips, but not all
com.apple.TipKit.ShowTips tipID,otherTipID

// Hide some tips, but not all
com.apple.TipKit.HideAllTips 1

// Hide all tips defined in the app
com.apple.TipKit.HideTips tipID,otherTipID

// Purge all TipKit related data
com.apple.TipKit.ResetDatastore 1