TipKit en Swift: mostrar tips de onboarding con reglas, frecuencia y TipView en SwiftUI

TipKit es el framework de Apple para mostrar tips contextuales de onboarding en apps iOS 17 y posteriores. La ventaja principal sobre gestionar manualmente UserDefaults o contadores de uso es que TipKit se encarga del almacenamiento, la frecuencia de visualización y la evaluación de reglas de forma declarativa.

Definir un tip: el protocolo Tip

Un tip es una estructura que conforma el protocolo Tip. El requisito mínimo es un title y, opcionalmente, un message e image:

import TipKit

struct TipFiltros: Tip {
    var title: Text {
        Text("Filtra tus resultados")
    }

    var message: Text? {
        Text("Usa el botón de filtro para mostrar solo las tareas del día de hoy.")
    }

    var image: Image? {
        Image(systemName: "line.3.horizontal.decrease.circle")
    }
}

Mostrar tips con TipView y popoverTip

Hay dos formas de mostrar un tip: inline con TipView o como popover con el modificador .popoverTip:

struct ListaTareas: View {
    private let tipFiltros = TipFiltros()

    var body: some View {
        VStack {
            // Tip inline: ocupa espacio en la jerarquía de vistas
            TipView(tipFiltros, arrowEdge: .bottom)

            List(tareas) { tarea in
                TareaRow(tarea: tarea)
            }
        }
        .toolbar {
            ToolbarItem {
                Button(action: mostrarFiltros) {
                    Image(systemName: "line.3.horizontal.decrease.circle")
                }
                // Tip como popover sobre el botón
                .popoverTip(tipFiltros, arrowEdge: .top)
            }
        }
    }
}

Reglas condicionales: Tips Rules

Los tips se muestran solo cuando se cumplen todas sus reglas. Existen dos tipos: reglas basadas en parámetros (@Parameter) y reglas basadas en eventos (Event):

struct TipExportacion: Tip {
    // Parámetro tipado almacenado automáticamente por TipKit
    @Parameter
    static var usuarioHaCreado3Tareas: Bool = false

    var title: Text { Text("Exporta tus tareas") }
    var message: Text? { Text("Ya tienes suficientes tareas para exportarlas a PDF.") }

    // El tip solo se muestra cuando la regla es verdadera
    var rules: [Rule] {
        #Rule(Self.$usuarioHaCreado3Tareas) { $0 == true }
    }
}

// En la app, cuando corresponda:
TipExportacion.usuarioHaCreado3Tareas = (TareaManager.shared.tareas.count >= 3)

Eventos acumulados: mostrar un tip tras N usos

Los eventos permiten contar acciones del usuario y mostrar el tip después de un umbral:

struct TipAccesoDirecto: Tip {
    // Evento que cuenta las veces que el usuario navega manualmente
    static let navegacionManual = Event(id: "navegacion.manual")

    var title: Text { Text("Usa el acceso directo") }
    var message: Text? { Text("Sabes que puedes deslizar para cambiar de sección, ¿verdad?") }

    var rules: [Rule] {
        // Mostrar tras 5 navegaciones manuales
        #Rule(Self.navegacionManual) { $0.donations.count >= 5 }
    }
}

// Cada vez que el usuario navega:
Task {
    await TipAccesoDirecto.navegacionManual.donate()
}

Frecuencia de visualización

Por defecto TipKit muestra un tip solo una vez. Se puede cambiar la frecuencia global al configurar TipKit:

@main
struct MiApp: App {
    init() {
        // Configurar TipKit al iniciar la app
        try? Tips.configure([
            .displayFrequency(.daily),   // Un tip por día como máximo
            .datastoreLocation(.applicationDefault)
        ])
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }
}

Las opciones de frecuencia disponibles son .immediate (sin límite), .hourly, .daily, .weekly y .monthly. La frecuencia afecta a todos los tips de la app, no a uno en concreto.

El antipatrón de resetDatastore en producción

Durante el desarrollo es habitual llamar a Tips.resetDatastore() para ver los tips de nuevo. Este método borra todos los datos de TipKit, incluyendo contadores de eventos y valores de parámetros. Nunca se debe llamar en el código de producción:

#if DEBUG
// Solo en desarrollo: resetear para probar tips ya vistos
try? Tips.resetDatastore()
#endif

try? Tips.configure([
    .displayFrequency(.immediate)  // En DEBUG, mostrar siempre
])

Invalidar un tip manualmente

Para marcar un tip como completado desde el código (por ejemplo, cuando el usuario ya realizó la acción que el tip sugería):

let tip = TipFiltros()
tip.invalidate(reason: .actionPerformed)

TipKit no mostrará ese tip de nuevo. Los motivos disponibles son .actionPerformed, .tipClosed y .displayCountExceeded.

Resumen

TipKit simplifica el onboarding contextual en iOS 17: define el tip con Tip, muéstralo con TipView o .popoverTip, controla cuándo aparece con reglas basadas en @Parameter o en eventos acumulados con Event.donate(), y ajusta la frecuencia global al configurar TipKit. El sistema gestiona automáticamente el almacenamiento y la deduplicación, eliminando la necesidad de gestionar manualmente los estados de onboarding.

COMPARTE ESTE ARTÍCULO

COMPARTIR EN FACEBOOK
COMPARTIR EN TWITTER
COMPARTIR EN LINKEDIN
COMPARTIR EN WHATSAPP