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
}
}
}
}
TipKit
RSS for tagIntelligently educate your users about the right features at the right time with TipKit
Posts under TipKit tag
32 Posts
Sort by:
Post
Replies
Boosts
Views
Activity
When I build app on Xcode 15.3 with SWIFT_STRICT_CONCURRENCY=complete, there are some warning.
Almost warning can be fixed, but not the TipKit code.
Here is the example.
Are there any good way to solve it?
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.
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)
...
}
}
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?
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?
I have created a tip with a parameter (Bool) I have tried to set it to true in a sheet which is presented modally over the view which should present the popover tip. After the sheet gets dismissed the popover tip is never presented. If I restart the app, the popover tip appears. Is there any way to trigger the presentation of a popover tip manually?
I have created a little demo app to demonstrate my problem:
Setup TipKit on app start:
import SwiftUI
import TipKit
@main
struct TipKitDemoApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.task {
try? Tips.configure()
}
}
}
}
Simple tip:
import Foundation
import TipKit
struct DemoTip: Tip {
@Parameter
static var enabled: Bool = false
var title: Text {
Text("Demo Tip")
}
var rules: [Rule] {
[
#Rule(Self.$enabled) { $0 == true }
]
}
}
Content view which includes the popover tip and displays the sheet where the tip can be enabled:
import SwiftUI
struct ContentView: View {
@State private var presentDetail = false
let demoTip = DemoTip()
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
.popoverTip(demoTip)
Button("Present Details") {
presentDetail.toggle()
}
}
.padding()
.sheet(isPresented: $presentDetail) {
DetailView()
}
}
}
In the detail view the tip gets enabled, but if I dismiss this view, the tip only appears after I restart the app:
import SwiftUI
struct DetailView: View {
@Environment(\.dismiss) private var dismiss
var body: some View {
Button("Enable demo tip") {
DemoTip.enabled = true
}
Button("Dismiss") {
dismiss()
}
}
}
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))
])
In iOS 17.1 (and 17.2 beta), the arrowEdge parameter of the SwiftUI popoverTip doesn't work anymore.
This code
button
.popoverTip(tip, arrowEdge: .bottom)
looks like this on iOS 17.0
and like this on 17.1 and up.
I checked permittedArrowDirections of the corresponding UIPopoverPresentationController (via the Memory Graph): It's .down on iOS 17.0 and .any (the default) on 17.1. It seems the parameter of popoverTip is not properly propagated to the popover controller anymore.
Hello!
I've tested/implemented TipKit in SwiftUI and UIKit but it seems that the close, i.e. X, button doesn't work in UIKit but does in SwiftUI. Not sure if this is a bug or I have to do something different about it in UIKit.
Testing with Xcode 15 Beta 8
Thanks!
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.
Hello! Based on the lack of forum posts, I think I'm one of the first people to really be diving into TipKit. :) I'm trying to use a tip to coax users toward a button in the toolbar of a NavigationView. The docs say to put the TipView "close to the content", but the best I can do for the NavigationView toolbar is to put it in one of the views inside the Navigation View itself. I'm using a TipView with an arrowEdge: .top parameter, which results in this:
I'd love to be able to move the arrow tip under the plus button. Is that possible in this early beta stage? Do I need to restructure my view hierarchy somehow?