Hi folks,
there's currently a known issue in TipKit due to which it won't show popover tips on buttons that are inside a SwiftUI ToolbarItem. For example, if you try this code, the popover tip will not appear:
ToolbarItem {
Button(action: {...}) {
Label("Tap here", systemImage: "gear")
}
.popoverTip(sampleTip)
}
There's an easy workaround for this issue. Just apply a style to the button. It can be any style. Some examples are bordered, borderless, plain and borderedProminent. Here's a fixed version of the above code:
ToolbarItem {
Button(action: {...}) {
Label("Tap here", systemImage: "gear")
}
.buttonStyle(.plain) // Adding this line fixes the issue.
.popoverTip(sampleTip)
}
Hope this helps anyone running into this issue.
TipKit
RSS for tagIntelligently educate your users about the right features at the right time with TipKit
Posts under TipKit tag
22 Posts
Selecting any option will automatically load the page
Post
Replies
Boosts
Views
Activity
The launch argument -com.apple.TipKit.HideAllTips 1 does not work if it is defined in xctestplan arguments passed on launch.
I tested it with the apple provided example app, where I created simple UI test and added xctestplan with launch argument -com.apple.TipKit.HideAllTips 1.
The app does not hide the tips when it is running the UI Tests.
Is there any solution that works?
Thanks for reply.
I'm adding Visual Intelligence support to my app, and now want to add a Tip using TipKit to guide users to this feature from within my app. I want to add a Rule to my Tip which will only show this Tip on devices where Visual Intelligence is supported (ex. not iPhone 14 Pro Max).
What is the best way for me to determine availability to set this TipKit rule?
Here's the documentation I'm following for Visual Intelligence: https://developer.apple.com/documentation/visualintelligence/integrating-your-app-with-visual-intelligence
Hello !
I manage to displays tips on tvOS no issue.
However when I want to add actions, they are not visible. Is there something specific to do here ?
I just provided the actions attributes to my tips.
The documentation does not say anything more.
Thanks!
I have been trying to use TipKit popovers in my App. They all behave as expected in the simulator but on a real device I am seeing some strange behaviors. I have a couple of instances of where the tip is displaying an entire sheet instead of just the popover. In another case I cannot dismiss the Tip. Has anyone seen these behaviors and are there known issues/workarounds for this?
I am reluctant to use something that works perfectly in the simulator but gives unpredictable results on a device. Not a good user experience in my opinion.
I've encountered a problem when placing a tip on an element in a ForEach loop. As long as there is only one element in the list the tip will be shown. But if there are more than one element the tip does not appear on iOS and iPadOS.
How do I get the tip to be displayed when several elements are displayed? Is it allowed to use the popoverTip() modifier in a ForEach loop or should it be avoided?
Interestingly, it works if you run the attached sample code on macOS. Then the tip is displayed on the “Third” element.
import SwiftUI
import TipKit
struct ContentView: View {
private var elements: [String] = ["First", "Second", "Third"]
let tip = DemoTip()
var body: some View {
NavigationStack {
List {
Section {
ForEach(elements, id: \.self) { element in
Text(element)
.popoverTip(tip)
}
}
}
}
}
}
struct DemoTip: Tip {
var title: Text {
Text("Demo Tip")
}
}
@main
struct TipKitTestApp: App {
init() {
#if DEBUG
Tips.showAllTipsForTesting()
#endif
try? Tips.configure([.displayFrequency(.immediate)])
}
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
I am trying to create a user flow where I can guide the user how to navigate through my app. I want to add a tip on a TabView that indicates user to navigate to a specific tab. I have seen this work with iOS properly but I am a little lost as VisionOS is not responding the same for .popoverTip etc. Any guidance is appreciated!
Greetings,
I have set up two tips in my app, and my app is configured with Tips.configure([.displayFrequency(.daily)].
Tip 1 is set up with no options. Tip 2 has the IgnoresDisplayFrequency(true) option set:
var options: [Option] {
MaxDisplayCount(3)
// We want the user to see these because it's important.
IgnoresDisplayFrequency(true)
}
This option works as expected, as far as I can tell, in terms of making sure that Tip 2 is shown even if I've already seen Tip 1 today. If I interact with my app such that Tip 1 is displayed, and I then interact with it such that Tip 2 should be displayed, Tip 2 shows immediately, even though a day hasn't passed.
However, if I do this the other way around, so that Tip 2 is displayed first, and then I interact so that Tip 1 should be displayed, my expectation would be that Tip 1 is not displayed, because another tip has already been shown today. I expected that it would not be shown until the following day, since it is not configured to ignore the tip frequency.
That's not what happens, though. Tip 1 is displayed right away, even though Tip 2 has just been shown. This makes me think that setting IgnoresDisplayFrequency on Tip 2 is causing it to also be ignored when considering whether other tips should be shown.
I did try omitting IgnoresDisplayFrequency option, and then as expected, only one tip is shown on a day, no matter which one is shown first.
I am noticing an issue that when .popoverTip() is presented, any tap gesture will only dismiss the tip and will not be passed down.
This means that if tip is applied to a button, tapping the button will only dismiss the tip but will not trigger the action.
Which logically breaks user expectation and defeats the whole point of a popover tip, as user will need to tap twice on the button to activate intended functionality.
Button("Settings", systemImage: "gear") {
// Will not trigger until tip is dismissed and button is tapped again.
showSettings.toggle()
}
.popoverTip(SettingsTip())
When using Tips.showAllTipsForTesting() my tips are shown repeatedly again and again after ~2 seconds on 18.4 and 18.5. It is not happening on iOS 18.1 (only tested with simulator)
Is this intended? It makes debugging very difficult.
Edit: Also, Tips.resetDatastore() does not seem to work as the documentation says?
Hi everyone,
I’m using a custom TipViewStyle to modify the background and slightly adjust the layout of the Tips in my app. Everything looked great until iOS 18.4. Since updating, the layout is being compressed, and the message inside the Tip is getting truncated.
Here’s a screenshot of how it looks on iOS 18.4 (truncated message)
and another showing how it used to look before iOS 18.4 (correct layout).
Here is the relevant code for the custom style:
struct CustomTipViewStyle: TipViewStyle {
func makeBody(configuration: Configuration) -> some View {
VStack(alignment: .leading, spacing: 4) {
HStack {
configuration.title?
.font(.headline)
.foregroundColor(.daBackground)
Spacer()
Button(action: { configuration.tip.invalidate(reason: .tipClosed) }) {
Image(systemName: "xmark")
.foregroundColor(.daBackground.opacity(0.3))
}
}
VStack(alignment: .leading, spacing: 8.0) {
configuration.message?
.font(.subheadline)
.foregroundColor(.daBackground.opacity(0.8))
Divider().background(.daBackground.opacity(0.3))
ForEach(configuration.actions) { action in
HStack {
Spacer()
Button(action: action.handler) {
action.label()
.foregroundStyle(.accent)
.font(.system(size: 18, weight: .bold))
}
}
}
}
}
.padding()
.background(Color.daBlack)
}
}
Has anyone else experienced this issue with TipViewStyle in iOS 18.4? Any workarounds or solutions would be appreciated!
Thanks in advance!
Step to reproduce:
I installed the example project on this page
I opened the Popover Tip View example
And clicked on the icon that should output to the console and invalidate the tip
Image(systemName: "wand.and.stars")
.imageScale(.large)
.popoverTip(popoverTip)
.onTapGesture {
print("test")
// Invalidate the tip when someone uses the feature.
popoverTip.invalidate(reason: .actionPerformed)
}
On version 17 with Tip presented, when I click on the button, I immediately get the output to the console and the tip disappears. On version 18, when I click on a button, the tip just disappears, it feels like it just overlaps everything and the clicks don't go any further. If anything the project is the same as in the example, I have only lowered the minimum version to 17.4.
As I understand there is a bug in iOS version 18, hence I have a question if there are ways to fix this?
Since beta 4, Using TipKit causes the app to crash.
This was not the case in Beta 3.
My app uses UIKit
Hi! I use Tips with CloudKit and it works very well, however when a user want to remove their data from CloudKit, how to do that?
In CoreData with CloudKit area, NSPersistentCloudKitContainer have purgeObjectsAndRecordsInZone to delete both local managed objects and CloudKit records, however there is no information about the TipKit deletion.
Does anyone know ideas?
Hi - I use TipKit in my App and AppClip. TipKit is configured with the app group's datastore. The tips show in the App, but on the AppClip, with the same rules/state, the tips do not display. Is this expected? TipKit is not listed as one of the frameworks unavailable to AppClips.
try? Tips.configure([
Tips.ConfigurationOption.displayFrequency(.hourly),
Tips.ConfigurationOption.datastoreLocation(.groupContainer(identifier: BuildConfiguration.shared.userDefaultsSuite))
])
Is there any way i can show popover tip on tabItem inside of TabView
TabView(selection: selected) {
Group{
HomeView()
.tabItem {
Label {
Text("Home")
} icon: {
Image(selected==1 ? "home-icon" : "home-unselect")
}
// show tip over the Home icon
}
.tag(1)
}
}
We have found a runtime crash using TipUIPopoverViewController because Xcode didn't warn about its usage when using a deployment target of iOS 16, without a proper #if available verification.
In Xcode Version 16.2 (16C5032a), using swift code, TipUIPopoverViewController can be used without if #available(iOS 17, *) without even trigger a warning or compiler error.
This is the snippet we're using in a UIViewController, and it compiles without a warning.
@objc
private func myMethod() {
if presentedViewController is TipUIPopoverViewController {
// do something
}
}
Of course this triggers a runtime error, specifically, a EXC_BAD_ACCESS.
I was expecting that the same way Xcode warns us when we're using Tips and Tips.Status with iOS 16, this would also trigger a compilation error.
I’m working on a solution to archive iMessages by using an API or similar mechanism. Here’s the desired workflow:
The user provides their phone number to initiate the archiving process.
They receive a text message with a URL link.
Clicking on the link authorizes the archiving of their iMessages.
Once authorized, their text messages are archived.
So far, I’ve researched third-party services and APIs but haven’t found any that offer this capability directly for iMessages.
Questions:
Are there any APIs or frameworks (Apple or third-party) that support accessing and archiving iMessages programmatically?
I have an observable object which is a model a view.
I also have a Tip (from TipKit) with @Parameter and a rule.
The source of truth for the state is in the observable object, however the Tip needs to be updated when state changes. So how do I bind between the two?
The way I was thinking was to sink updates from objectWillChange and check if Tips parameter needs to be updated or add didSet if it a @Published property. But I am not sure this is the best practice.
Schematic example:
class MyModel: ObservableObject {
struct TapSubmitTip: Tip {
@Parameter static var isSubmitButtonEnabled: Bool = false
var title: Text {
Text("Tap \"Submit\" to confirm your selection.")
}
var rules: [Rule] {
#Rule(Self.$isSubmitButtonEnabled) {
$0 == true
}
}
}
let tapSubmitTip = TapSubmitTip()
var objectWillChangeCancallable: AnyCancellable!
// Used by the view to enable or disable the button
var isSubmitButtonEnabled: Bool {
// Some logic to determine if button should be enabled.
}
init() {
objectWillChangeCancallable = objectWillChange.sink { [weak self] void in
guard let self else { return }
if isSubmitButtonEnabled {
TapSubmitTip.isSubmitButtonEnabled = true
}
}
}
...
// Call objectWillChange or update published properties as needed.
...
}
My feature has rule:
var rules: [Rule] {
#Rule(Self.didOpenProductDetails) { event in
event.donations.count == 0
}
}
Actual:
After first donation is done it suppose to be performed - but after I back to screen the Tip is showing again. Only after second donation the Tip dismisses.
Expected:
After first donation, even donations count == 1 and Tip won't be presented again.
Tested on iOS 18.1
I have a tip that's supposed to be shown after a certain event:
extension Tips {
static let somethingHappened = Tip.Event(id: "somethingHappened")
}
struct TestTip: Tip {
let title = Text("Test tip")
let message = Text("This is a description")
var rules: [Rule] {
#Rule(Tips.somethingHappened) { $0.donations.count > 0 }
}
}
I would like to present this tip when its status becomes .available. To do this, I'm observing statusUpdates in a task:
tipStatusObserverTask = Task { [tip, weak self] in
print("observing \(tip.id)")
for await status in tip.statusUpdates {
guard let self, !Task.isCancelled else {
return
}
print("tip status: \(status)")
if case .available = status {
print("will present \(tip.id)")
displayTip()
}
}
print("done observing \(tip.id)")
}
then I'm donating the event on a button press:
@objc func didPressButton() {
Tips.somethingHappened.sendDonation {
print("donated Tips.somethingHappened")
}
}
The event is donated, but statusUpdates doesn't fire, so the tip never gets shown. The tip appears after I restart the app. What's happening? What am I doing wrong?
statusUpdates fires just fine if the rule is based on a @Parameter change, so I would expect this approach to work for event-based rules.
Here's a project that reproduces the issue in case anyone wants to try: https://www.icloud.com/iclouddrive/085FxcgTPStDSSPoXwh7dx1pQ#TipKitTest