TipKit
Adicione dicas de descoberta de funcionalidades, dicas contextuais e coach marks de onboarding em apps iOS 17+ usando o framework TipKit da Apple. O TipKit gerencia a frequência de exibição, regras de elegibilidade e persistência para que as dicas apareçam no momento certo e desapareçam quando o usuário aprender a funcionalidade.
Conteúdo
- Configuração
- Definindo Dicas
- Exibindo Dicas
- Regras de Dicas
- Ações de Dicas
- Grupos de Dicas
- Controle Programático
- Erros Comuns
- Checklist de Revisão
- Referências
Configuração
Chame Tips.configure() uma vez em App.init, antes que qualquer view seja renderizada. Isso
inicializa o armazenamento de dados das dicas e começa a avaliação das regras. Chamá-lo depois
arrisca uma condição de corrida onde as views de dica tentam exibir antes que o armazenamento esteja pronto.
import SwiftUI
import TipKit
@main
struct MyApp: App {
init() {
try? Tips.configure([
.datastoreLocation(.applicationDefault)
])
}
var body: some Scene {
WindowGroup { ContentView() }
}
}
Opções de DatastoreLocation
| Opção | Caso de Uso |
|---|---|
.applicationDefault | Localização padrão, sandbox do app (a maioria dos apps) |
.groupContainer(identifier:)) | Compartilhar estado das dicas entre app e extensões |
.url(_:) | URL de arquivo personalizada para controle total sobre a localização do armazenamento |
Sincronização com CloudKit
Sincronize o estado das dicas entre os dispositivos do usuário para que ele não veja a mesma dica em todos os dispositivos. Adicione a opção de contêiner CloudKit junto com a localização do armazenamento.
try? Tips.configure([
.datastoreLocation(.applicationDefault),
.cloudKitContainer(.named("iCloud.com.example.app"))
])
Definindo Dicas
Conforme uma struct ao protocolo Tip. Forneça um title no mínimo.
Adicione message para detalhes de suporte e image para um ícone principal. Mantenha os
títulos curtos e orientados à ação, pois a dica aparece como um pequeno callout.
import SwiftUI
import TipKit
struct FavoriteTip: Tip {
var title: Text { Text("Fixar Seus Favoritos") }
var message: Text? { Text("Toque no ícone de coração para salvar itens para acesso rápido.") }
var image: Image? { Image(systemName: "heart") }
}
Propriedades: title (obrigatório), message (detalhe opcional), image (ícone principal opcional), actions (botões opcionais), rules (condições de elegibilidade opcionais), options (frequência de exibição, contagem máxima).
Ciclo de Vida: Pendente (regras insatisfeitas) -> Elegível (todas as regras passam) -> Invalidada (descartada, acionada ou removida programaticamente). Uma vez invalidada, uma dica não reaparece a menos que o armazenamento seja redefinido.
Exibindo Dicas
Dicas Inline com TipView
Incorpore uma TipView diretamente no seu layout. Ela renderiza como um cartão arredondado que
aparece e desaparece com animação. Use para dicas dentro de conteúdo rolável.
let favoriteTip = FavoriteTip()
var body: some View {
VStack {
TipView(favoriteTip)
ItemListView()
}
}
Dicas Popover com .popoverTip()
Anexe uma dica como um popover ancorado a qualquer view. O framework desenha uma seta do popover para a âncora. Use para dicas apontando para um controle específico.
Button { toggleFavorite() } label: { Image(systemName: "heart") }
.popoverTip(favoriteTip)
// Controlar direção da seta (omitir para deixar o sistema escolher)
.popoverTip(favoriteTip, arrowEdge: .bottom)
Estilo Personalizado de TipViewStyle
Crie um estilo personalizado para controlar a aparência das dicas em todo o app. Conforme
a TipViewStyle e implemente makeBody(configuration:).
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()
}
}
// Aplicar globalmente ou por view
TipView(favoriteTip)
.tipViewStyle(CustomTipStyle())
Regras de Dicas
As regras controlam quando uma dica se torna elegível. Todas as regras no array rules
devem passar antes que a dica seja exibida. O TipKit suporta dois tipos de regra:
baseadas em parâmetros e baseadas em eventos.
Regras Baseadas em Parâmetros
Use @Parameter para rastrear o estado do app. A dica se torna elegível quando o
valor do parâmetro satisfaz a condição da regra.
struct FavoriteTip: Tip {
@Parameter
static var hasSeenList: Bool = false
var title: Text { Text("Fixar Seus Favoritos") }
var rules: [Rule] {
#Rule(Self.$hasSeenList) { $0 == true }
}
}
// Definir o parâmetro quando o usuário chegar à lista
FavoriteTip.hasSeenList = true
Regras Baseadas em Eventos
Use Tips.Event para rastrear ações do usuário. Contribua para o evento cada vez que a
ação ocorrer. A regra é acionada quando a condição de contagem ou tempo de contribuição
é atendida. Isso é ideal para dicas que devem aparecer depois que o usuário realizou uma
ação várias vezes sem descobrir um recurso relacionado.
struct ShortcutTip: Tip {
static let appOpenedEvent = Tips.Event(id: "appOpened")
var title: Text { Text("Experimente a Ação Rápida") }
var rules: [Rule] {
#Rule(Self.appOpenedEvent) { $0.donations.count >= 3 }
}
}
// Contribuir cada vez que o app abrir
ShortcutTip.appOpenedEvent.donate()
Combinando Várias Regras
Coloque várias regras no array. Todas devem passar (E lógico).
struct AdvancedTip: Tip {
@Parameter
static var isLoggedIn: Bool = false
static let featureUsedEvent = Tips.Event(id: "featureUsed")
var title: Text { Text("Desbloquear Modo Avançado") }
var rules: [Rule] {
#Rule(Self.$isLoggedIn) { $0 == true }
#Rule(Self.featureUsedEvent) { $0.donations.count >= 5 }
}
}
Opções de Frequência de Exibição
Controle com que frequência as dicas aparecem usando a propriedade options.
struct DailyTip: Tip {
var title: Text { Text("Lembrete Diário") }
var options: [TipOption] {
MaxDisplayCount(3) // Mostrar no máximo 3 vezes no total
IgnoresDisplayFrequency(true) // Ignorar o limite global de frequência
}
}
A frequência de exibição global é definida no momento da configuração:
try? Tips.configure([
.displayFrequency(.daily) // .immediate, .hourly, .daily, .weekly, .monthly
])
Com .daily, o sistema mostra no máximo uma dica por dia em todo o app,
a menos que uma dica específica defina IgnoresDisplayFrequency(true).
Ações de Dicas
Adicione botões de ação a uma dica para interação direta. Cada ação tem um id
e um rótulo. Lide com a ação no manipulador de ações da view de dica.
struct FeatureTip: Tip {
var title: Text { Text("Experimente o Novo Editor") }
var message: Text? { Text("Adicionamos um novo modo de edição poderoso.") }
var actions: [Action] {
Action(id: "open-editor", title: "Abrir Editor")
Action(id: "learn-more", title: "Saiba Mais")
}
}
Lide com as ações na view:
TipView(featureTip) { action in
switch action.id {
case "open-editor":
navigateToEditor()
featureTip.invalidate(reason: .actionPerformed)
case "learn-more":
showHelpSheet = true
default:
break
}
}
Grupos de Dicas
Use TipGroup para coordenar múltiplas dicas dentro de uma única view.
TipGroup garante que apenas uma dica do grupo seja exibida por vez,
prevenindo sobrecarga de dicas. As dicas são exibidas em ordem de prioridade.
struct OnboardingView: View {
let tipGroup = TipGroup(.ordered) {
WelcomeTip()
NavigationTip()
ProfileTip()
}
var body: some View {
VStack {
if let currentTip = tipGroup.currentTip {
TipView(currentTip)
}
Button("Próximo") {
tipGroup.currentTip?.invalidate(reason: .actionPerformed)
}
}
}
}
Opções de Prioridade
| Inicializador | Comportamento |
|---|---|
.ordered | As dicas são exibidas na ordem em que são listadas |
Quando a dica atual é invalidada, a próxima dica elegível no grupo
torna-se currentTip.
Controle Programático
Invalidando Dicas
Chame invalidate(reason:) quando o usuário realizar a ação descoberta ou
quando a dica não for mais relevante.
let tip = FavoriteTip()
tip.invalidate(reason: .actionPerformed)
| Razão | Quando Usar |
|---|---|
.actionPerformed | O usuário realizou a ação que a dica descreve |
.displayCountExceeded | A dica atingiu seu número máximo de exibições |
.tipClosed | O usuário descartou explicitamente a dica |
Utilitários de Teste
O TipKit fornece métodos estáticos para controlar a visibilidade das dicas durante o desenvolvimento
e testes. Proteja-os com verificações #if DEBUG ou ProcessInfo para que nunca
sejam executados em builds de produção.
#if DEBUG
// Mostrar todas as dicas independentemente das regras (útil durante o desenvolvimento)
Tips.showAllTipsForTesting()
// Mostrar apenas dicas específicas
Tips.showTipsForTesting([FavoriteTip.self, ShortcutTip.self])
// Ocultar todas as dicas (útil para testes de UI que não envolvem dicas)
Tips.hideAllTipsForTesting()
// Redefinir o armazenamento de dados (limpa todo o estado das dicas, invalidações e eventos)
try? Tips.resetDatastore()
#endif
Usando ProcessInfo para Esquemas de Teste
if ProcessInfo.processInfo.arguments.contains("--show-all-tips") {
Tips.showAllTipsForTesting()
}
Passe --show-all-tips como um argumento de inicialização no esquema Xcode para
builds de desenvolvimento.
Erros Comuns
NÃO FAÇA: Chamar Tips.configure() em qualquer lugar exceto App.init
Chamar Tips.configure() em um modificador onAppear ou task de uma view
cria uma condição de corrida onde as views de dica tentam renderizar antes que o
armazenamento esteja pronto, causando dicas ausentes ou piscando.
// ERRADO
struct ContentView: View {
var body: some View {
Text("Olá")
.task { try? Tips.configure() } // Tarde demais, views já renderizadas
}
}
// CORRETO
@main struct MyApp: App {
init() { try? Tips.configure() }
var body: some Scene { WindowGroup { ContentView() } }
}
NÃO FAÇA: Mostrar muitas dicas ao mesmo tempo
Exibir várias dicas simultaneamente sobrecarrega os usuários e dilui o impacto de cada dica. Os usuários aprendem a ignorá-las.
// ERRADO: Três dicas visíveis ao mesmo tempo
VStack {
TipView(tipA)
TipView(tipB)
TipView(tipC)
}
// CORRETO: Use TipGroup para sequenciá-las
let group = TipGroup(.ordered) { TipA(); TipB(); TipC() }
if let currentTip = group.currentTip {
TipView(currentTip)
}
NÃO FAÇA: Esquecer de invalidar dicas após o usuário realizar a ação
Se uma dica diz "Toque na estrela para favoritar" e o usuário toca na estrela mas a dica permanece, isso corrói a confiança na UI.
// ERRADO: Dica permanece visível após o usuário agir
Button("Favoritar") { toggleFavorite() }
.popoverTip(favoriteTip)
// CORRETO: Invalidar na ação
Button("Favoritar") {
toggleFavorite()
favoriteTip.invalidate(reason: .actionPerformed)
}
.popoverTip(favoriteTip)
NÃO FAÇA: Deixar dicas de teste habilitadas em produção
Tips.showAllTipsForTesting() ignora todas as regras e limites de frequência.
Enviar isso em produção significa que todos os usuários veem todas as dicas imediatamente.
// ERRADO: Sempre ativo
Tips.showAllTipsForTesting()
// CORRETO: Protegido por DEBUG
#if DEBUG
Tips.showAllTipsForTesting()
#endif
NÃO FAÇA: Criar títulos de dicas muito longos
Títulos longos são truncados ou quebrados de forma estranha no pequeno callout da dica. Coloque a ação principal no título e o contexto de suporte na mensagem.
// ERRADO
var title: Text { Text("Você pode tocar no botão de coração para salvar este item na sua lista de favoritos") }
// CORRETO
var title: Text { Text("Salvar nos Favoritos") }
var message: Text? { Text("Toque no ícone de coração para manter itens para acesso rápido.") }
NÃO FAÇA: Usar dicas para informações críticas
Os usuários podem descartar dicas a qualquer momento e elas não reaparecem. Nunca coloque instruções essenciais ou informações de segurança em uma dica.
// ERRADO: Informação crítica em uma dica descartável
struct DataLossTip: Tip {
var title: Text { Text("Alterações não salvas serão perdidas") }
}
// CORRETO: Use um alerta ou aviso inline para informações críticas
// Reserve dicas para descoberta de funcionalidades e divulgação progressiva
Checklist de Revisão
-
Tips.configure()chamado emApp.init, antes que qualquer view seja renderizada - Cada dica tem um título claro e conciso (orientado à ação, abaixo de ~40 caracteres)
- Dicas invalidadas quando o usuário realiza a ação descoberta
- Regras definidas para que as dicas apareçam no momento certo (não imediatamente no primeiro lançamento para todas as dicas)
-
TipGroupusado quando múltiplas dicas existem em uma visão - Utilitários de teste (
showAllTipsForTesting,resetDatastore) protegidos atrás de#if DEBUG - Sincronização CloudKit configurada se o app suportar múltiplos dispositivos
- Frequência de exibição definida adequadamente (
.dailyou.weeklypara a maioria dos apps) - Dicas usadas apenas para descoberta de funcionalidades, não para informações críticas
-
TipViewStylepersonalizado aplicado consistentemente se o estilo padrão não corresponder ao design do app - Ações de dicas tratadas e dica invalidada no manipulador de ações
- Contribuições de eventos colocadas nos pontos corretos de ação do usuário
- Garantir que tipos Tip personalizados sejam Sendable; configurar Tips em @MainActor
Referências
- Consulte referências/tipkit-patterns.md para padrões de implementação completos incluindo estilos personalizados, regras baseadas em eventos, grupos de dicas, estratégias de teste, fluxos de onboarding e configuração de pré-visualização SwiftUI.