Intelligently educate your users about the right features at the right time with TipKit

Posts under TipKit tag

33 Posts
Sort by:

Post

Replies

Boosts

Views

Activity

TipKit: showing a popover tip on a SwiftUI toolbar button
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.
9
8
4.1k
Sep ’24
TipViewStyle not compiling
I'm creating a simple TipViewStyle based on sample code but it fails to compile. It displays: Type 'MyViewStyle' does not conform to protocol 'TipViewStyle' When I choose the Fix option, it adds this line: `type alias Body = type' What should the type be here? struct MyTipViewStyle: TipViewStyle { func makeBody(config: Configuration) -> some View { VStack { config.title config.message? } }
2
0
132
4d
How to dismiss a TipView on tvOS
I have added a "welcome" tip to my SwiftUI app, which only appears on the main screen the first time the app is launched. On macOS and iOS, the TipView has an X button that lets the user dismiss the tip. However, on tvOS there is no such button, and I cannot figure out how to dismiss the tip at all. Using the remote, I am unable to navigate to the tip and highlight it so I can click it to dismiss. Pressing the Home remote button while the tip is displayed has no effect other than closing my app and going back to the tvOS launch screen. Am I missing something? struct ContentView: View { @Environment(TempestDataProvider.self) private var dataProvider @State private var welcomeTip = WelcomeTip() var body: some View { VStack { Grid { GridRow { TemperatureMetricBox(alignment: .leading, backgroundStyle: nil, bottomPadding: true) WindMetricBox(alignment: .trailing, backgroundStyle: nil, bottomPadding: true) } GridRow { HumidityMetricBox(alignment: .leading, backgroundStyle: nil, bottomPadding: true) PressureMetricBox(alignment: .trailing, backgroundStyle: nil, bottomPadding: true) } GridRow { RainMetricBox(alignment: .leading, backgroundStyle: nil, bottomPadding: true) SunMetricBox(alignment: .trailing, backgroundStyle: nil, bottomPadding: true) } GridRow { LightningMetricBox(alignment: .leading, backgroundStyle: nil, bottomPadding: true) MetricBox(alignment: .trailing, systemImageName: "sensor", backgroundStyle: nil, bottomPadding: true) { IndicatorLightPanel() } } } .fixedSize(horizontal: false, vertical: true) Spacer() TipView(welcomeTip) StatusBar() } } }
1
0
150
4w
iOS 17 and Order TipKit Tips
Hello, I've been trying to imagine how to support ensuring the display of my tips in the order I want them to for iOS 17. I am familiar with the TipGroup iOS18 feature, but I'm looking to control the order without TipGroup so I can deliver a great user experience in my iOS 17 and > app. I've tried lots of theories, but can't seem to figure it out and I don't see anyone else having solved it. Any ideas/code examples anyone could point me to? Thanks!
0
2
382
Oct ’24
Tip on Menu in Toolbar not showing
Hey, I have a use case where I want to show a tip for a menu thats in a toolbar. Here's my approach to attach the tip to the Menu. I also tried to attach it to the Button in the menu but in both cases the tip was not shown. None of the described fixes in this post worked for me, adding .buttonStyle(.borderless) didn't help either ... .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Menu { Button { // ... } label: { // ... } } label: { Image(systemName: "heart") } .popoverTip(Tip()) } } I'm working on a visionOS app
1
1
363
Jul ’24
TipKit: explicit vs. implicit iCloud sync
With iOS 18, TipKit got explicit support for syncing tip state via iCloud. However, before that, TipKit already did iCloud syncing implicitly, as far as I know. How does the new explicit syncing relate to the previous mechanism? Do we have to enable iCloud syncing manually now to retain the functionality in iOS 18? Is there a way to sync with the state that was already stored by TipKit in iCloud on iOS 17?
1
0
492
Jul ’24
TipKit: Popover Tip w/ use of Menu in Toolbar
As the title states, I'm trying to apply a .popoverTip to a Menu { } which is inside of a .toolbar { }. The toolbar has default placement and the menu includes a toggle button, and a NavigationLink. I've ensured that tips can show on the view by using a TipView(tip: ) within my view which displays. Am I missing something? Is this not possible? Alternatively, can anyone recommend a method to potentially debug why a tip won't show for future debugging?
1
0
580
Jun ’24
TipKit vs. Swift 6 + Concurrency
I'm trying to convert my project to use Swift 6 with Complete Concurrency in Xcode 16 beta 1. The project uses TipKit, but I'm getting compile errors when trying to use the TipKit Parameters feature. Here is an example of the type of error I'm seeing (Note that this code from https://developer.apple.com/documentation/tipkit/highlightingappfeatureswithtipkit): struct ParameterRuleTip: Tip { // Define the app state you want to track. @Parameter static var isLoggedIn: Bool = false Static property '$isLoggedIn' is not concurrency-safe because it is non-isolated global shared mutable state. Is there a new pattern for supporting TipKit Parameters in Swift 6 with Complete Concurrency enabled? There is no obvious suggestion for how to fix this. The latest WWDC 2024 TipKit doesn't appear to have any solution(s).
7
2
960
Jul ’24
TipKit: Are tips marked "IgnoresDisplayFrequency" also ignored by display frequency?
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.
0
1
464
May ’24
Appkit :: TIPKIT :: TipNSPopover :: Tippopover view is not closing when we clicked on the cross icon in MAC OS application.
By Implementing the TipKit framework in the AppKit Mac OS application, we were unable to close the popover when we clicked on the cross icon presented in TipNSPopover view. And any clues about how to implement the TipNSPopover on Handover of NScollectionview ?. When the mouse enters any of the collection view cells, I have to present the TipNSpopover view. Please share your input and thoughts.
1
0
496
May ’24
TipKit: "The filter function is not supported in this rule."
Greetings, Working on adding some simple rules to my first TipKit tips. I'm following the example from the WWDC TipKit talk, which included a rule intended to ensure that the user had accessed a particular feature at least three times in the past five days. So it had code that looked like this: #Rule(Self.enteredBackyardDetailView) { $0.donations .filter { $0.date > Date.now.addingTimeInterval(-5 * 60 * 60 * 24) } .count >= 3 I'm trying to do something similar -- essentially, I want to know if the user has been using this feature for at least a day. So I tried this: #Rule(Self.viewedDetails) { $0.donations .filter { $0.date < Date.now.addingTimeInterval(-1 * 60 * 60 * 24) } .count > 0 } i.e., Is there at least one event that was donated more than a day ago? However, Xcode flags the .filter line with the message "The filter function is not supported in this rule." Huh? This smells to me a lot like the limitations that SwiftData has with not being able to do certain kinds of operations in predicates. But this was clearly intended to be supported in the design, since the exact example was shown in the WWDC session on TipKit. So, am I missing something? Is there a different way to structure this so that I can use .filter, or is there some other way of expressing the condition without using filter? I did try using .first { same expression } != nil, but Xcode then said that rules must include a count comparison... Thanks!
3
1
546
May ’24
TipKit: Change color of TipView close button?
Greetings, I'm dipping a toe into TipKit and trying it out. I placed a TipView into one of my SwiftUI views, and the default styling didn't fit well within our app, so I changed the background and text colors. However, I can't find a way to change the color of the close button ("X"). Unfortunately, the default color is very hard to see against the background color that I selected. Is it possible to set the color of that close button? For the time being, I have created a TipViewStyle that replicates the default styling with my color changes applied, but this seems unnecessarily complex for something as simple as setting the color of that button. Thanks!
2
0
710
Jun ’24
When the TipKit notification appears, the 'present sheet' button will be non-functional. (iPhoneSE Landscape Right)
import SwiftUI import TipKit @main struct TipKit_WithPresentPageApp: App { var body: some Scene { WindowGroup { ContentView() .task { try? Tips.resetDatastore() try? Tips.configure([ .datastoreLocation(.applicationDefault) ]) } } } } import SwiftUI struct ContentView: View { @State private var isPresented: Bool = false var body: some View { NavigationStack { VStack { Image(systemName: "globe") .imageScale(.large) .foregroundStyle(.tint) .popoverTip(MyTip()) .padding(100) Button("Hit Me!") { isPresented.toggle() // When the TipKit notification appears, the 'present sheet' button will be non-functional. (iPhone SE and simulator devices) } .padding() .sheet(isPresented: $isPresented) { PresentPage() } } } } } import SwiftUI struct PresentPage: View { var body: some View { Text("Hello, world again!") .font(.title) } } import TipKit struct MyTip: Tip { var title: Text { Text("Test") } var message: Text? { Text("Hi") } } When the TipKit notification appears, the 'present sheet' button will be non-functional. (iPhoneSE Landscape Right) When using the iPhone SE (Landscape Right) or its simulator (iPhone SE Landscape Right), running iOS 17.2. Whenever the TipKit notification is triggered and displayed on the screen, the 'present sheet' button, which is typically used for presenting a new sheet within the app, becomes non-functional. Device: iPhoneSE iOS 17.2 Does anyone know how to bypass this bug? Thank you.
0
0
425
May ’24
TipUIPopoverViewController's imageSize doesn't work
Per the apple API documentation (https://developer.apple.com/documentation/tipkit/tipuipopoverviewcontroller/imagesize), imageSize is meant to control the size of the image within the tip, but it doesn't seem to be working. My code is as follows, taken from the apple docs example: tipObservationTask = tipObservationTask ?? Task { @MainActor [weak controller] in for await shouldDisplay in tip.shouldDisplayUpdates { if shouldDisplay { let popoverController = TipUIPopoverViewController(tip, sourceItem: sourceItem) popoverController.imageSize = CGSize(width: 10, height: 10) controller?.present(popoverController, animated: true) tipPopoverController = popoverController } else { if controller?.presentedViewController is TipUIPopoverViewController { controller?.dismiss(animated: true) tipPopoverController = nil } } } }
0
0
436
May ’24
Ability to prevent `.popoverTip` from being dismissed on scroll
I recently integrated TipKit's .popoverTip to hint users about key features in my app, but find they are too easily oversaw and dismissible: as soon as they appear if i tap outside or scroll they disappear and many users don't event realise they're here and skip them, without ever seing them again since they have been dismissed. Is there a way to make them more persistent as the TipView is? I tried using TipView but it doesn't fit my use case since the feature I want to tip about is a button in the top right side of a toolbar, TipView's arrow doesn't point to the feature so there isn't a lot of context between the tip and the button itself.
0
2
398
Apr ’24
Sharing a TipKit database between two
Hi all, I work on a macOS app that's actually split into two apps: one primary app and one "helper" app that lives in the menubar and runs in the background when the primary app is quit. Recently, I've been integrating TipKit into these apps, and I'd like to have one unified TipKit database shared between them. I set up TipKit in both apps' AppDelegate classes with the datastoreLocation set to the apps' shared Group Containers folder. I've verified with a SQLite DB viewer that both apps can store event donations and parameters as well as tip status in that shared database. However, updates to the TipKit database are not being propagated between the two apps. For example, I have a TipKit event that only the "helper" app donates to, and I use that event for a TipKit rule in a Tip displayed in the primary app, but the tip only displays after I restart the primary app instead of immediately once the rule requirements are met (I've verified that the helper is properly making donations to that event). Unfortunately, combining both apps into one app is out of the question (in the near term, anyways). Is there anything I'm missing here to get cross-app TipKit communication to work? Here's the relevant code (truncated and with variable and class names altered for IP reasons): TipKitConstants.swift (accessible by both apps in a shared framework) import TipKit // MARK: - DataStore @available(macOS 14.0, *) extension Tips.ConfigurationOption.DatastoreLocation { public static let sharedGroupContainer = Tips.ConfigurationOption.DatastoreLocation.url(NSURL.sharedGroupContainer()) // This NSURL extension points to a location in group containers that both apps can write to } // MARK: - Events @available (macOS 14, *) public struct TipKitEvents { ... public static let helperEvent = Tips.Event(id: "helperEvent") ... } ... PrimaryAppDelegate+TipKit.swift (app delegate is in obj-c, hence the extension) import TipKit extension PrimaryAppDelegate { @objc func setupTips() { if #available(macOS 14, *) { ... try? Tips.configure([ .displayFrequency(.immediate), .datastoreLocation(.sharedGroupContainer) ]) } } } HelperAppDelegate+TipKit.swift (app delegate is in obj-c, hence the extension) extension HelperAppDelegate { @objc func setupTips() { if #available(macOS 14, *) { try? Tips.configure([ .displayFrequency(.immediate), .datastoreLocation(.sharedGroupContainer) ]) } } } HelperClass+TipKit.swift (this is the class where the event donation happens) import CommonFramework extension HelperClass { @objc func donateHelperEvent() { if #available(macOS 14, *) { Task(priority: .background) { await TipKitEvents.helperEvent.donate() } } } ... } ExampleTip.swift (exists in the primary app) @available(macOS 14, *) struct ExampleTip: Tip { ... // All Tip protocol requirements are implemented above var rules: [Rule] { [#Rule(TipKitEvents.helperEvent) { $0.donations.count >= 3 }] } ... } PrimaryAppWindowController.h @interface EditorWindowController : NSWindowController ... // TipKit types are not representable in Objective-C, hence all the "id" types here @property id templateCreationTip; @property id templateCreationTipObservationTask; @property id templateCreationTipPopover; ... PrimaryAppWindowController.m @implementation PrimaryAppWindowController ... - (void)windowDidLoad { [self setUpTips]; } ... PrimaryAppWindowController+TipKit.swift @available(macOS 14, *) extension PrimaryAppWindowController { @objc func setUpTips() { if exampleTip == nil { exampleTip = ExampleTip() } exampleTipObservationTask = exampleTipObservationTask ?? Task { @MainActor in if let tip = exampleTip as? ExampleTip { for await shouldDisplay in tip.shouldDisplayUpdates { if shouldDisplay { showExampleTip() } else { (exampleTipPopover as? TipNSPopover)?.close() exampleTipPopover = nil } } } } } @objc func showExampleTip() { guard let exampleTip = exampleTip as? ExampleTip, let buttonView = window?.toolbar?.items.filter({ $0.itemIdentifier.rawValue == ItemIdentifier.button }).first?.view else { return } exampleTipPopover = TipNSPopover(exampleTip) (exampleTipPopover as? TipNSPopover)?.show(relativeTo: buttonView.bounds, of: buttonView, preferredEdge: .maxY) ... } }
0
1
441
Apr ’24
TipKit: How to let the View know that a tip is shown
I implemented TipKit in my app, but I ran into this problem: I want every tap that happens when a tip is open only close the tip and nothing else. Only the next taps should interfere with the View that called the tip. I have a game where you tap on a sort of hidden object, so when you tap on a wrong place it generates an error. When the tip is open and you accidentally tap on the game (or want to close the tip that way) the game reads this as intentional game tap and counts that as an error. With other overlaying views I have a Bool property that prevents this behavior, because the game 'knows' that a tap is only legit if this Bool is false. But I can't figure out how to 'let the game know' that a tip is open. Is there a way how I can read if a tip is shown or already invalidated?
1
0
600
Apr ’24
Tip Event for high-occurrence Event
TipKit stores all Event donations including their Date locally. I am wondering if it's still good practise to have an Event for a action that happens a lot of times in my app (the main feature), since Power-Users could get over a million triggers of this event. This would result into millions of entries, but I only need a rule counting if the count is higher then 3. Would it be smarter to think of a different approach for this use-case to save storage (and maybe query speed), or is it still advised to use Events for something like this?
1
0
454
Mar ’24