How to force TipKit to show a tip, regardless of history
Asked Answered
U

1

6

I'm curious, is there a way to get TipKit to show a tip on-demand in a reliable way? I want a tip to appear only when the user taps on a help button, but I can't get it to happen consistently (ie after every single tap).

In this example code, it will work once on each launch, only because I've called Tips.resetDatastore() on initialisation. I would normally not want to be calling that. I'd rather that my Tooltip didn't store or use event history at all. But it should still respond to new events.

import SwiftUI
import TipKit

struct extraHelp: Tip {
    static let tipRequested = Event(id: "extraHelpRequested")
    
    var title: Text { Text("Extra Help") }
    var message: Text { Text("Suitable amount of explanation here.")}
    
    var rules: [Rule] { [
        #Rule(Self.tipRequested) { $0.donations.count > 0 }
    ]}
    
    // This tip will always be visible when event received
    var options: [Option] {
        [IgnoresDisplayFrequency(true)]
    }
}


struct ViewWithOnScreenHelp: View {
    @State private var onscreenHelp = false
    
    var body: some View {
        VStack {
            Button("Help!") {
                Task { await extraHelp.tipRequested.donate() }
            }
            .popoverTip(extraHelp())
        }
        .padding()
        .task { // This .task would normally go on the app root-view
            try? Tips.resetDatastore()     // not normal use
            try? Tips.configure([
                .displayFrequency(.immediate),
                .datastoreLocation(.applicationDefault),
            ])
        }
    }
}

I know that this isn't the way Apple wants me to use TipKit, but it's the UX I have in mind and it would be a shame to not be able to use the parts they've supplied.

Understandable answered 27/11, 2023 at 21:27 Comment(6)
I did some research a while ago and you need to set the trigger and then use something else to redraw the view. It is poor design from Apple since you can’t “present” on demand. Donate something then redraw and it will show.Quartet
That's disappointing that the design is so poor! I tried this, and it worked for a bit. But subsequent relaunches didn't work, so I'm not sure I have fully understood your solution. If you (or anyone else) has the time to do a full Answer with working code, that would be very much appreciated!Understandable
Here is a sample I worked out for someone else, they want subsequent tips but the setup resets too. #77152167Quartet
You could try Tips.showTipsForTesting([myTip]), but, as the name suggests, this is meant to be used for testing only. Not sure if it will work in release builds. You should definitely file Feedback, asking for an official way to do what you want. I think it's a good use case!Rheostat
Just filed an enhancement request under FB13418895. Mirroring appreciated!Rheostat
Thanks, Frank. I will do so, as soon as I have a developer account.Understandable
H
0

It's totally feasible, just not as intuitive as you might like. A workaround is to use a unique ID in rule macros:

struct SomeTip: Tip {
  let id: String
  let titleString: String
  let messageString: String
  @Parameter static var shownTips: [String: Bool] = [:]

  var title: Text {
    Text(titleString)
  }

  var message: Text? {
    Text(messageString)
  }

  var rules: [Rule] {
    [
      #Rule(Self.$shownTips) { tip in
        tip[id] == true
      }
    ]
  }

  var options: [TipOption] {
    [
      Tip.IgnoresDisplayFrequency(true)
    ]
  }
}
Hairstyle answered 13/8 at 17:36 Comment(1)
Thank you! This looks interesting. To make it clear, could you also provide the View that would use SomeTip, please?Understandable

© 2022 - 2024 — McMap. All rights reserved.