AppIntents en Swift: Shortcuts, integración con Siri, AppShortcut y parámetros

AppIntents, disponible desde iOS 16, es el framework de Apple para exponer funcionalidades de tu app a Shortcuts, Siri y el sistema de acciones rápidas. Sustituye al antiguo SiriKit con un modelo declarativo mucho más flexible: cualquier función de la app se puede convertir en un intent en pocas líneas.

Crear un intent básico: AppIntent

Un intent es una estructura que conforma el protocolo AppIntent. El requisito mínimo es un title y el método perform():

import AppIntents

struct AbrirTareaIntent: AppIntent {
    static var title: LocalizedStringResource = "Abrir tarea"
    static var description = IntentDescription("Abre una tarea específica de la app.")

    func perform() async throws -> some IntentResult {
        // Navegar a la lista de tareas
        return .result()
    }
}

Para parámetros, usa el wrapper @Parameter:

struct CrearTareaIntent: AppIntent {
    static var title: LocalizedStringResource = "Crear tarea"

    @Parameter(title: "Título de la tarea")
    var titulo: String

    @Parameter(title: "Fecha límite")
    var fechaLimite: Date?

    @Parameter(title: "Prioridad", default: .media)
    var prioridad: Prioridad

    func perform() async throws -> some IntentResult & ProvidesDialog {
        let tarea = TareaManager.shared.crear(
            titulo: titulo,
            fecha: fechaLimite,
            prioridad: prioridad
        )
        return .result(dialog: "Tarea '(tarea.titulo)' creada.")
    }
}

@Parameter con tipos personalizados

Para que los parámetros acepten tipos de tu app en Shortcuts, el tipo debe conformar AppEnum (para enumeraciones) o AppEntity (para entidades con búsqueda):

enum Prioridad: String, AppEnum {
    case alta, media, baja

    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Prioridad"
    static var caseDisplayRepresentations: [Prioridad: DisplayRepresentation] = [
        .alta: "Alta",
        .media: "Media",
        .baja: "Baja",
    ]
}

AppShortcutsProvider: atajos predefinidos

Los AppShortcut son atajos que aparecen automáticamente en Shortcuts sin que el usuario los configure manualmente. Se definen en un AppShortcutsProvider:

struct MiAppShortcuts: AppShortcutsProvider {
    static var appShortcuts: [AppShortcut] {
        AppShortcut(
            intent: CrearTareaIntent(),
            phrases: [
                "Crear tarea en (.applicationName)",
                "Nueva tarea con (.applicationName)",
            ],
            shortTitle: "Crear tarea",
            systemImageName: "plus.circle"
        )

        AppShortcut(
            intent: MostrarTareasPendientesIntent(),
            phrases: [
                "Ver tareas pendientes en (.applicationName)",
                "Qué tareas tengo en (.applicationName)",
            ],
            shortTitle: "Tareas pendientes",
            systemImageName: "checklist"
        )
    }
}

Las frases con (.applicationName) son obligatorias en al menos una frase por shortcut para que Siri pueda activarlos con el nombre de la app.

EntityQuery: seleccionar entidades en Shortcuts

Para que el usuario pueda elegir una entidad concreta de tu app (por ejemplo, una tarea específica), implementa AppEntity y EntityQuery:

struct TareaEntity: AppEntity {
    let id: UUID
    let titulo: String
    let completada: Bool

    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Tarea"
    static var defaultQuery = TareaQuery()

    var displayRepresentation: DisplayRepresentation {
        DisplayRepresentation(title: "(titulo)")
    }
}

struct TareaQuery: EntityQuery {
    func entities(for identifiers: [UUID]) async throws -> [TareaEntity] {
        TareaManager.shared.tareas
            .filter { identifiers.contains($0.id) }
            .map { TareaEntity(id: $0.id, titulo: $0.titulo, completada: $0.completada) }
    }

    func suggestedEntities() async throws -> [TareaEntity] {
        TareaManager.shared.tareasPendientes()
            .prefix(5)
            .map { TareaEntity(id: $0.id, titulo: $0.titulo, completada: $0.completada) }
    }
}

struct CompletarTareaIntent: AppIntent {
    static var title: LocalizedStringResource = "Completar tarea"

    @Parameter(title: "Tarea")
    var tarea: TareaEntity

    func perform() async throws -> some IntentResult & ProvidesDialog {
        TareaManager.shared.completar(id: tarea.id)
        return .result(dialog: "'(tarea.titulo)' marcada como completada.")
    }
}

ControlWidget en iOS 18

iOS 18 añade los Controls al Centro de Control. Usan la misma API de AppIntents:

import WidgetKit

struct ControlActivarModoFoco: ControlWidget {
    var body: some ControlWidgetConfiguration {
        StaticControlConfiguration(
            kind: "com.miapp.foco",
            provider: ProveedorFoco()
        ) { value in
            ControlWidgetToggle(
                "Modo Foco",
                isOn: value,
                action: ActivarFocoIntent()
            ) { isOn in
                Label(isOn ? "Foco activo" : "Foco inactivo",
                      systemImage: isOn ? "moon.fill" : "moon")
            }
        }
    }
}

Errores frecuentes

El error más habitual al empezar con AppIntents es olvidar añadir la extensión de Shortcuts al target en Xcode. Los intents deben estar en el target principal de la app, no solo en el target de extensión. Otro error es no incluir (.applicationName) en alguna frase del shortcut: Siri lo requiere para poder activar el shortcut por voz.

Resumen

AppIntents moderniza la integración de apps iOS con Shortcuts y Siri. Un AppIntent con @Parameter expone funciones de la app; AppShortcutsProvider define atajos listos para usar sin configuración; AppEntity y EntityQuery permiten seleccionar objetos propios de la app en los flujos de Shortcuts; y los ControlWidget de iOS 18 llevan los intents al Centro de Control. La curva de aprendizaje es baja en comparación con el antiguo SiriKit y el resultado es notablemente más potente.

COMPARTE ESTE ARTÍCULO

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