TipKit
Füge Feature-Entdeckungstipps, kontextbezogene Hinweise und Onboarding-Coach-Marks zu iOS 17+ Apps hinzu, indem du Apple's TipKit-Framework verwendest. TipKit verwaltet die Anzeigehäufigkeit, Berechtigungsregeln und Persistenz, sodass Tipps im richtigen Moment erscheinen und verschwinden, sobald der Benutzer die Funktion gelernt hat.
Inhalt
- Einrichtung
- Tipps definieren
- Tipps anzeigen
- Tipp-Regeln
- Tipp-Aktionen
- Tipp-Gruppen
- Programmatische Steuerung
- Häufige Fehler
- Review-Checkliste
- Referenzen
Einrichtung
Rufe Tips.configure() einmal in App.init auf, bevor irgendeine Ansicht gerendert wird. Dadurch wird der Tipp-Datenspeicher initialisiert und die Regelauswertung beginnt. Ein späterer Aufruf riskiert eine Race-Bedingung, bei der Tipp-Ansichten versuchen anzuzeigen, bevor der Datenspeicher bereit ist.
import SwiftUI
import TipKit
@main
struct MyApp: App {
init() {
try? Tips.configure([
.datastoreLocation(.applicationDefault)
])
}
var body: some Scene {
WindowGroup { ContentView() }
}
}
Datenspeicher-Optionen
| Option | Anwendungsfall |
|---|---|
.applicationDefault | Standardort, App-Sandbox (die meisten Apps) |
.groupContainer(identifier:) | Tipp-Status zwischen App und Erweiterungen teilen |
.url(_:) | Benutzerdefinierte Datei-URL für volle Kontrolle über den Speicherort |
CloudKit-Synchronisation
Synchronisiere den Tipp-Status über die Geräte eines Benutzers, damit er denselben Tipp nicht auf jedem Gerät sieht. Füge die CloudKit-Container-Option zusammen mit dem Datenspeicherort hinzu.
try? Tips.configure([
.datastoreLocation(.applicationDefault),
.cloudKitContainer(.named("iCloud.com.example.app"))
])
Tipps definieren
Erstelle eine Struktur, die dem Tip-Protokoll entspricht. Gib mindestens einen title an. Füge message für unterstützende Details und image für ein führendes Symbol hinzu. Halte Titel kurz und handlungsorientiert, da der Tipp als kompakte Sprechblase erscheint.
import TipKit
struct FavoriteTip: Tip {
var title: Text { Text("Favoriten anheften") }
var message: Text? { Text("Tippe auf das Herz-Symbol, um Elemente für schnellen Zugriff zu speichern.") }
var image: Image? { Image(systemName: "heart") }
}
Eigenschaften: title (erforderlich), message (optionales Detail), image (optionales führendes Symbol), actions (optionale Buttons), rules (optionale Berechtigungsbedingungen), options (Anzeigehäufigkeit, maximale Anzahl).
Lebenszyklus: Ausstehend (Regeln nicht erfüllt) -> Berechtigt (alle Regeln erfüllt) -> Ungültig (geschlossen, bearbeitet oder programmatisch entfernt). Ein einmal ungültig gemachter Tipp erscheint nicht wieder, es sei denn, der Datenspeicher wird zurückgesetzt.
Tipps anzeigen
Inline-Tipps mit TipView
Bette eine TipView direkt in dein Layout ein. Sie wird als abgerundete Karte dargestellt, die mit Animation erscheint und verschwindet. Verwende sie für Tipps in scrollbaren Inhalten.
let favoriteTip = FavoriteTip()
var body: some View {
VStack {
TipView(favoriteTip)
ItemListView()
}
}
Popover-Tipps mit .popoverTip()
Binde einen Tipp als Popover an eine beliebige Ansicht. Das Framework zeichnet einen Pfeil vom Popover zum Anker. Verwende dies für Tipps, die auf ein bestimmtes Steuerelement zeigen.
Button { toggleFavorite() } label: { Image(systemName: "heart") }
.popoverTip(favoriteTip)
// Pfeilrichtung steuern (weglassen, damit das System wählt)
.popoverTip(favoriteTip, arrowEdge: .bottom)
Benutzerdefinierter TipViewStyle
Erstelle einen benutzerdefinierten Stil, um das Erscheinungsbild von Tipps in der gesamten App zu steuern. Implementiere das TipViewStyle-Protokoll und dessen makeBody(configuration:)-Methode.
struct CustomTipStyle: TipViewStyle {
func makeBody(configuration: Configuration) -> some View {
HStack(spacing: 12) {
configuration.image?
.font(.title2)
.foregroundStyle(.tint)
VStack(alignment: .leading, spacing: 4) {
configuration.title
.font(.headline)
configuration.message?
.font(.subheadline)
.foregroundStyle(.secondary)
}
}
.padding()
}
}
// Global oder pro Ansicht anwenden
TipView(favoriteTip)
.tipViewStyle(CustomTipStyle())
Tipp-Regeln
Regeln steuern, wann ein Tipp berechtigt wird. Alle Regeln im rules-Array müssen erfüllt sein, bevor der Tipp angezeigt wird. TipKit unterstützt zwei Regeltypen: parameterbasierte und ereignisbasierte.
Parameterbasierte Regeln
Verwende @Parameter, um den App-Status zu verfolgen. Der Tipp wird berechtigt, wenn der Parameterwert die Regelbedingung erfüllt.
struct FavoriteTip: Tip {
@Parameter
static var hasSeenList: Bool = false
var title: Text { Text("Favoriten anheften") }
var rules: [Rule] {
#Rule(Self.$hasSeenList) { $0 == true }
}
}
// Setze den Parameter, wenn der Benutzer die Liste erreicht
FavoriteTip.hasSeenList = true
Ereignisbasierte Regeln
Verwende Tips.Event, um Benutzeraktionen zu verfolgen. Spende jedes Mal an das Ereignis, wenn die Aktion ausgeführt wird. Die Regel feuert, wenn die Spendenanzahl oder der Zeitpunkt erfüllt ist. Dies ist ideal für Tipps, die erscheinen sollen, nachdem der Benutzer eine Aktion mehrmals ausgeführt hat, ohne eine verwandte Funktion entdeckt zu haben.
struct ShortcutTip: Tip {
static let appOpenedEvent = Tips.Event(id: "appOpened")
var title: Text { Text("Probiere die Schnellaktion") }
var rules: [Rule] {
#Rule(Self.appOpenedEvent) { $0.donations.count >= 3 }
}
}
// Spende jedes Mal, wenn die App geöffnet wird
ShortcutTip.appOpenedEvent.donate()
Mehrere Regeln kombinieren
Füge mehrere Regeln in das Array ein. Alle müssen erfüllt sein (logisches UND).
struct AdvancedTip: Tip {
@Parameter
static var isLoggedIn: Bool = false
static let featureUsedEvent = Tips.Event(id: "featureUsed")
var title: Text { Text("Erweiterten Modus freischalten") }
var rules: [Rule] {
#Rule(Self.$isLoggedIn) { $0 == true }
#Rule(Self.featureUsedEvent) { $0.donations.count >= 5 }
}
}
Anzeigehäufigkeitsoptionen
Steuere, wie oft Tipps erscheinen, mit der options-Eigenschaft.
struct DailyTip: Tip {
var title: Text { Text("Tägliche Erinnerung") }
var options: [TipOption] {
MaxDisplayCount(3) // Maximal 3 Mal insgesamt anzeigen
IgnoresDisplayFrequency(true) // Globale Häufigkeitsbegrenzung umgehen
}
}
Die globale Anzeigehäufigkeit wird bei der Konfiguration festgelegt:
try? Tips.configure([
.displayFrequency(.daily) // .immediate, .hourly, .daily, .weekly, .monthly
])
Mit .daily zeigt das System höchstens einen Tipp pro Tag in der gesamten App an, es sei denn, ein bestimmter Tipp setzt IgnoresDisplayFrequency(true).
Tipp-Aktionen
Füge Aktions-Buttons zu einem Tipp hinzu, um direkte Interaktion zu ermöglichen. Jede Aktion hat eine id und ein Label. Behandle die Aktion im Action-Handler der Tippansicht.
struct FeatureTip: Tip {
var title: Text { Text("Probiere den neuen Editor") }
var message: Text? { Text("Wir haben einen leistungsstarken neuen Bearbeitungsmodus hinzugefügt.") }
var actions: [Action] {
Action(id: "open-editor", title: "Editor öffnen")
Action(id: "learn-more", title: "Mehr erfahren")
}
}
Aktionen in der Ansicht behandeln:
TipView(featureTip) { action in
switch action.id {
case "open-editor":
navigateToEditor()
featureTip.invalidate(reason: .actionPerformed)
case "learn-more":
showHelpSheet = true
default:
break
}
}
Tipp-Gruppen
Verwende TipGroup, um mehrere Tipps innerhalb einer einzelnen Ansicht zu koordinieren. TipGroup stellt sicher, dass immer nur ein Tipp aus der Gruppe gleichzeitig angezeigt wird, um Tipp-Überlastung zu vermeiden. Tipps werden in priorisierter Reihenfolge angezeigt.
struct OnboardingView: View {
let tipGroup = TipGroup(.ordered) {
WelcomeTip()
NavigationTip()
ProfileTip()
}
var body: some View {
VStack {
if let currentTip = tipGroup.currentTip {
TipView(currentTip)
}
Button("Weiter") {
tipGroup.currentTip?.invalidate(reason: .actionPerformed)
}
}
}
}
Prioritätsoptionen
| Initialisierer | Verhalten |
|---|---|
.ordered | Tipps werden in der Reihenfolge angezeigt, in der sie aufgelistet sind |
Wenn der aktuelle Tipp ungültig gemacht wird, wird der nächste berechtigte Tipp in der Gruppe zum currentTip.
Programmatische Steuerung
Tipps ungültig machen
Rufe invalidate(reason:) auf, wenn der Benutzer die entdeckte Aktion ausführt oder der Tipp nicht mehr relevant ist.
let tip = FavoriteTip()
tip.invalidate(reason: .actionPerformed)
| Grund | Wann verwenden |
|---|---|
.actionPerformed | Benutzer hat die im Tipp beschriebene Aktion ausgeführt |
.displayCountExceeded | Tipp hat seine maximale Anzeigeanzahl erreicht |
.tipClosed | Benutzer hat den Tipp explizit geschlossen |
Test-Hilfsmittel
TipKit bietet statische Methoden, um die Tippsichtbarkeit während der Entwicklung und des Testens zu steuern. Schütze diese mit #if DEBUG oder ProcessInfo-Prüfungen, damit sie nie in Produktions-Builds ausgeführt werden.
#if DEBUG
// Alle Tipps anzeigen, unabhängig von Regeln (nützlich während der Entwicklung)
Tips.showAllTipsForTesting()
// Nur bestimmte Tipps anzeigen
Tips.showTipsForTesting([FavoriteTip.self, ShortcutTip.self])
// Alle Tipps ausblenden (nützlich für UI-Tests, die keine Tipps betreffen)
Tips.hideAllTipsForTesting()
// Datenspeicher zurücksetzen (löscht alle Tipp-Status, Ungültigmachungen und Ereignisse)
try? Tips.resetDatastore()
#endif
Verwenden von ProcessInfo für Testschemata
if ProcessInfo.processInfo.arguments.contains("--show-all-tips") {
Tips.showAllTipsForTesting()
}
Füge --show-all-tips als Startargument im Xcode-Schema für Entwicklungs-Builds hinzu.
Häufige Fehler
NICHT: Tips.configure() an einer anderen Stelle als App.init aufrufen
Ein Aufruf von Tips.configure() in einem onAppear- oder task-Modifier einer Ansicht erzeugt eine Race-Bedingung, bei der Tipp-Ansichten versuchen zu rendern, bevor der Datenspeicher bereit ist, was zu fehlenden oder flackernden Tipps führt.
// FALSCH
struct ContentView: View {
var body: some View {
Text("Hallo")
.task { try? Tips.configure() } // Zu spät, Ansichten wurden bereits gerendert
}
}
// RICHTIG
@main struct MyApp: App {
init() { try? Tips.configure() }
var body: some Scene { WindowGroup { ContentView() } }
}
NICHT: Zu viele Tipps auf einmal anzeigen
Das gleichzeitige Anzeigen mehrerer Tipps überfordert Benutzer und verwässert die Wirkung jedes einzelnen Tipps. Benutzer lernen, sie zu ignorieren.
// FALSCH: Drei Tipps gleichzeitig sichtbar
VStack {
TipView(tipA)
TipView(tipB)
TipView(tipC)
}
// RICHTIG: Verwende TipGroup, um sie zu sequenzieren
let group = TipGroup(.ordered) { TipA(); TipB(); TipC() }
if let currentTip = group.currentTip {
TipView(currentTip)
}
NICHT: Vergessen, Tipps ungültig zu machen, nachdem der Benutzer die Aktion ausgeführt hat
Wenn ein Tipp sagt "Tippe auf den Stern, um zu favorisieren" und der Benutzer tippt auf den Stern, aber der Tipp bleibt, untergräbt dies das Vertrauen in die UI.
// FALSCH: Tipp bleibt sichtbar, nachdem der Benutzer gehandelt hat
Button("Favorisieren") { toggleFavorite() }
.popoverTip(favoriteTip)
// RICHTIG: Bei Aktion ungültig machen
Button("Favorisieren") {
toggleFavorite()
favoriteTip.invalidate(reason: .actionPerformed)
}
.popoverTip(favoriteTip)
NICHT: Test-Tipps in der Produktion aktiviert lassen
Tips.showAllTipsForTesting() umgeht alle Regeln und Häufigkeitsbegrenzungen. Wenn dies in der Produktion ausgeliefert wird, sieht jeder Benutzer sofort jeden Tipp.
// FALSCH: Immer aktiv
Tips.showAllTipsForTesting()
// RICHTIG: Hinter DEBUG geschützt
#if DEBUG
Tips.showAllTipsForTesting()
#endif
NICHT: Tipp-Titel zu lang machen
Lange Titel werden in der kompakten Tipp-Sprechblase abgeschnitten oder umgebrochen. Setze die Hauptaktion in den Titel und den unterstützenden Kontext in die Nachricht.
// FALSCH
var title: Text { Text("Du kannst auf den Herz-Button tippen, um dieses Element zu deiner Favoritenliste hinzuzufügen") }
// RICHTIG
var title: Text { Text("Zu Favoriten speichern") }
var message: Text? { Text("Tippe auf das Herz-Symbol, um Elemente für schnellen Zugriff zu behalten.") }
NICHT: Tipps für kritische Informationen verwenden
Benutzer können Tipps jederzeit schließen und sie erscheinen nicht wieder. Gib niemals wichtige Anweisungen oder Sicherheitsinformationen in einem Tipp.
// FALSCH: Kritische Info in einem schließbaren Tipp
struct DataLossTip: Tip {
var title: Text { Text("Ungespeicherte Änderungen gehen verloren") }
}
// RICHTIG: Verwende einen Alert oder Inline-Warnhinweis für kritische Informationen
// Hebe Tipps für Feature-Entdeckung und progressive Offenlegung auf
Review-Checkliste
-
Tips.configure()inApp.initaufgerufen, bevor Ansichten gerendert werden - Jeder Tipp hat einen klaren, prägnanten Titel (handlungsorientiert, unter ~40 Zeichen)
- Tipps ungültig gemacht, wenn der Benutzer die entdeckte Aktion ausführt
- Regeln so gesetzt, dass Tipps zum richtigen Zeitpunkt erscheinen (nicht sofort beim ersten Start für alle Tipps)
-
TipGroupverwendet, wenn mehrere Tipps in einer Ansicht existieren - Test-Hilfsmittel (
showAllTipsForTesting,resetDatastore) hinter#if DEBUGgeschützt - CloudKit-Synchronisation konfiguriert, wenn die App mehrere Geräte unterstützt
- Anzeigehäufigkeit angemessen eingestellt (
.dailyoder.weeklyfür die meisten Apps) - Tipps nur für Feature-Entdeckung verwendet, nicht für kritische Informationen
- Benutzerdefinierter
TipViewStyleeinheitlich angewendet, wenn der Standardstil nicht zum App-Design passt - Tipp-Aktionen behandelt und Tipp im Action-Handler ungültig gemacht
- Ereignisspenden an den richtigen Benutzeraktionspunkten platziert
- Sicherstellen, dass benutzerdefinierte Tip-Typen Sendable sind; Tips auf @MainActor konfigurieren
Referenzen
- Siehe references/tipkit-patterns.md für vollständige Implementierungsmuster einschließlich benutzerdefinierter Stile, ereignisbasierter Regeln, Tipp-Gruppen, Teststrategien, Onboarding-Abläufe und SwiftUI-Vorschaukonfiguration.