App Intents y Siri Shortcuts: automatización avanzada de iOS 2026
Guía completa de App Intents y Siri Shortcuts para iOS 18+. Crear acciones personalizadas para Siri, integrar Apple Intelligence y automatizar la app Swift en 2026.

En 2026, con Apple Intelligence y el framework App Intents, las aplicaciones iOS entran en una era donde la intención del usuario importa más que la interfaz gráfica. Las apps que no exponen intents se vuelven invisibles en un sistema operativo orientado a la IA. App Intents constituye la base que permite a Siri, Spotlight, los widgets y el Action Button interactuar con las funcionalidades de la aplicación.
Este artículo presenta la creación completa de App Intents y Siri Shortcuts para iOS 18+, desde los conceptos fundamentales hasta la integración con Apple Intelligence y los App Intent Domains.
Comprender el framework App Intents
El framework App Intents, introducido con iOS 16, moderniza la construcción de intents en Swift al reemplazar el antiguo SiriKit Intents framework. Esta arquitectura declarativa permite crear acciones detectables por el sistema: Spotlight, app Shortcuts, Siri y el Action Button.
import AppIntents
// Un AppIntent representa una acción que el usuario puede ejecutar
struct CreateTaskIntent: AppIntent {
// Título mostrado en Shortcuts y Siri
static var title: LocalizedStringResource = "Crear una tarea"
// Descripción para accesibilidad y sugerencias
static var description = IntentDescription(
"Crea una nueva tarea en la aplicación."
)
// Parámetro con validación automática
@Parameter(title: "Título de la tarea")
var taskTitle: String
// Parámetro opcional con valor por defecto
@Parameter(title: "Prioridad", default: .medium)
var priority: TaskPriority
// Ejecución de la acción
func perform() async throws -> some IntentResult & ReturnsValue<TaskEntity> {
// Crear la tarea mediante el servicio
let task = TaskService.shared.createTask(
title: taskTitle,
priority: priority
)
// Retornar la entidad creada para encadenar
return .result(value: TaskEntity(task: task))
}
}El intent declara sus parámetros mediante el property wrapper @Parameter, permitiendo a Siri solicitar los valores faltantes. El método perform() ejecuta la lógica de negocio y retorna un resultado tipado.
Definir App Entities para los datos
Las App Entities representan los "sustantivos" de la aplicación: los objetos sobre los que operan los intents. Permiten a Siri comprender y manipular los datos de la app.
import AppIntents
// Modelo de datos interno
struct Task: Identifiable, Codable {
let id: UUID
var title: String
var priority: TaskPriority
var isCompleted: Bool
var dueDate: Date?
}
// Entidad expuesta al sistema
struct TaskEntity: AppEntity {
// Identificador único requerido
var id: UUID
// Propiedades visibles
var title: String
var priority: TaskPriority
var isCompleted: Bool
// Configuración de visualización en el sistema
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Tarea"
// Representación visual de la instancia
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(
title: "\(title)",
subtitle: "\(priority.rawValue)",
image: .init(systemName: isCompleted ? "checkmark.circle.fill" : "circle")
)
}
// Query por defecto para buscar entidades
static var defaultQuery = TaskEntityQuery()
// Inicializador desde el modelo interno
init(task: Task) {
self.id = task.id
self.title = task.title
self.priority = task.priority
self.isCompleted = task.isCompleted
}
}
// Query para buscar y filtrar entidades
struct TaskEntityQuery: EntityQuery {
// Búsqueda por identificadores
func entities(for identifiers: [UUID]) async throws -> [TaskEntity] {
TaskService.shared.fetchTasks()
.filter { identifiers.contains($0.id) }
.map { TaskEntity(task: $0) }
}
// Sugerencias mostradas en la interfaz
func suggestedEntities() async throws -> [TaskEntity] {
TaskService.shared.fetchTasks()
.filter { !$0.isCompleted }
.prefix(5)
.map { TaskEntity(task: $0) }
}
}La EntityQuery define cómo el sistema busca y sugiere entidades. Los métodos entities(for:) y suggestedEntities() alimentan las interfaces de Siri y Shortcuts.
Usar AppEnum para tipos con un conjunto fijo de valores (prioridad, estado), y AppEntity para tipos dinámicos creados por el usuario (tareas, notas, contactos).
Crear App Enums para valores fijos
Los App Enums exponen tipos enumerados al sistema, permitiendo a Siri ofrecer opciones contextuales.
import AppIntents
// Enum expuesto al sistema
enum TaskPriority: String, AppEnum, Codable {
case low
case medium
case high
// Nombre del tipo mostrado
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Prioridad"
// Representación de cada caso
static var caseDisplayRepresentations: [TaskPriority: DisplayRepresentation] = [
.low: DisplayRepresentation(
title: "Baja",
image: .init(systemName: "arrow.down.circle")
),
.medium: DisplayRepresentation(
title: "Media",
image: .init(systemName: "minus.circle")
),
.high: DisplayRepresentation(
title: "Alta",
image: .init(systemName: "exclamationmark.circle")
)
]
}
// Enum para el estado de la tarea
enum TaskStatus: String, AppEnum, Codable {
case pending
case inProgress
case completed
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Estado"
static var caseDisplayRepresentations: [TaskStatus: DisplayRepresentation] = [
.pending: "Pendiente",
.inProgress: "En progreso",
.completed: "Completada"
]
}Las representaciones visuales (iconos SF Symbols) enriquecen la visualización en Shortcuts y en las sugerencias de Siri.
Implementar AppShortcutsProvider
El AppShortcutsProvider expone los App Shortcuts al sistema, dejándolos disponibles inmediatamente sin configuración del usuario. Estos atajos aparecen en Spotlight, Siri y el Action Button.
import AppIntents
// Provider que declara todos los shortcuts de la app
struct TaskAppShortcutsProvider: AppShortcutsProvider {
// Máximo 10 shortcuts por aplicación
@AppShortcutsBuilder
static var appShortcuts: [AppShortcut] {
// Shortcut para crear una tarea
AppShortcut(
intent: CreateTaskIntent(),
phrases: [
// El placeholder .applicationName es OBLIGATORIO
"Crear una tarea con \(.applicationName)",
"Nueva tarea en \(.applicationName)",
"Añadir una tarea a \(.applicationName)"
],
shortTitle: "Crear tarea",
systemImageName: "plus.circle"
)
// Shortcut para listar tareas
AppShortcut(
intent: ListTasksIntent(),
phrases: [
"Mostrar mis tareas en \(.applicationName)",
"Ver tareas de \(.applicationName)",
"Cuáles son mis tareas \(.applicationName)"
],
shortTitle: "Mis tareas",
systemImageName: "list.bullet"
)
// Shortcut con parámetro dinámico
AppShortcut(
intent: CompleteTaskIntent(),
phrases: [
"Completar \(\.$taskName) en \(.applicationName)",
"Marcar \(\.$taskName) como hecha con \(.applicationName)"
],
shortTitle: "Completar tarea",
systemImageName: "checkmark.circle"
)
}
}Las frases de voz deben incluir el placeholder \(.applicationName) para que Siri identifique la app destino. Los parámetros dinámicos como \(\.$taskName) permiten comandos contextuales.
Una aplicación puede declarar como máximo 10 App Shortcuts. Conviene priorizar las acciones más frecuentes y útiles para los usuarios.
Intents con parámetros complejos
Los intents pueden aceptar parámetros complejos, incluyendo entidades y opciones de configuración.
import AppIntents
struct CompleteTaskIntent: AppIntent {
static var title: LocalizedStringResource = "Completar una tarea"
static var description = IntentDescription(
"Marca una tarea como completada."
)
// Parámetro tipo entidad con búsqueda automática
@Parameter(title: "Tarea")
var task: TaskEntity
// Parámetro opcional con fecha
@Parameter(title: "Fecha de finalización")
var completionDate: Date?
// Configuración del diálogo Siri
static var parameterSummary: some ParameterSummary {
Summary("Completar \(\.$task)") {
\.$completionDate
}
}
func perform() async throws -> some IntentResult & ProvidesDialog {
// Actualiza la tarea
TaskService.shared.completeTask(
id: task.id,
completionDate: completionDate ?? Date()
)
// Feedback de voz para Siri
return .result(
dialog: "La tarea \(task.title) ha sido marcada como completada."
)
}
}
// Intent que retorna una lista de entidades
struct ListTasksIntent: AppIntent {
static var title: LocalizedStringResource = "Listar tareas"
// Filtro opcional
@Parameter(title: "Estado", default: nil)
var statusFilter: TaskStatus?
@Parameter(title: "Prioridad", default: nil)
var priorityFilter: TaskPriority?
func perform() async throws -> some IntentResult & ReturnsValue<[TaskEntity]> {
var tasks = TaskService.shared.fetchTasks()
// Aplicar filtros
if let status = statusFilter {
tasks = tasks.filter {
switch status {
case .completed: return $0.isCompleted
case .pending, .inProgress: return !$0.isCompleted
}
}
}
if let priority = priorityFilter {
tasks = tasks.filter { $0.priority == priority }
}
let entities = tasks.map { TaskEntity(task: $0) }
return .result(value: entities)
}
}El ParameterSummary define cómo Siri presenta el intent durante la ejecución por voz, ofreciendo un feedback natural.
¿Listo para aprobar tus entrevistas de iOS?
Practica con nuestros simuladores interactivos, flashcards y tests técnicos.
Integración con Apple Intelligence
iOS 18 introduce los App Intent Domains, colecciones de APIs diseñadas para funcionalidades específicas. Estos dominios permiten a Apple Intelligence comprender y ejecutar acciones con mayor precisión.
import AppIntents
// Conformidad con el dominio Bookmarks para integración Apple Intelligence
struct SaveBookmarkIntent: AppIntent {
static var title: LocalizedStringResource = "Guardar un marcador"
// Parámetro URL con validación automática
@Parameter(title: "URL")
var url: URL
@Parameter(title: "Título", default: nil)
var title: String?
@Parameter(title: "Carpeta", default: nil)
var folder: BookmarkFolderEntity?
// Abrir la app si es necesario
static var openAppWhenRun: Bool = false
func perform() async throws -> some IntentResult & ProvidesDialog {
let bookmark = BookmarkService.shared.save(
url: url,
title: title,
folder: folder?.id
)
return .result(
dialog: "Marcador guardado: \(bookmark.title)"
)
}
}
// Intent con conciencia del contenido en pantalla (iOS 18.4+)
struct AnalyzeScreenContentIntent: AppIntent {
static var title: LocalizedStringResource = "Analizar contenido"
// Acceso al contexto de pantalla vía Apple Intelligence
@Parameter(title: "Contexto")
var screenContext: String?
func perform() async throws -> some IntentResult & ProvidesDialog {
guard let context = screenContext else {
return .result(dialog: "No hay contenido que analizar.")
}
// Procesar contenido extraído por Apple Intelligence
let analysis = ContentAnalyzer.analyze(context)
return .result(dialog: analysis.summary)
}
}Los dominios predefinidos (Books, Camera, Spreadsheets) permiten a Siri responder a las solicitudes con precisión gracias a modelos entrenados en estas tareas específicas.
Acciones Siri con confirmación del usuario
Para acciones sensibles, el sistema puede solicitar confirmación antes de la ejecución.
import AppIntents
struct DeleteTaskIntent: AppIntent {
static var title: LocalizedStringResource = "Eliminar una tarea"
@Parameter(title: "Tarea")
var task: TaskEntity
// Requiere confirmación del usuario
static var isDiscoverable: Bool = true
func perform() async throws -> some IntentResult & ProvidesDialog {
// Solicitar confirmación mediante diálogo
try await requestConfirmation(
result: .result(
dialog: "¿Confirmas que quieres eliminar \(task.title)?"
)
)
// Eliminar tras confirmación
TaskService.shared.deleteTask(id: task.id)
return .result(
dialog: "La tarea \(task.title) ha sido eliminada."
)
}
}
// Intent con varias etapas de diálogo
struct ScheduleTaskIntent: AppIntent {
static var title: LocalizedStringResource = "Programar una tarea"
@Parameter(title: "Tarea")
var task: TaskEntity
@Parameter(title: "Fecha")
var scheduledDate: Date
@Parameter(title: "Recordatorio", default: true)
var setReminder: Bool
static var parameterSummary: some ParameterSummary {
When(\.$setReminder, .equalTo, true) {
Summary("Programar \(\.$task) para \(\.$scheduledDate) con recordatorio")
} otherwise: {
Summary("Programar \(\.$task) para \(\.$scheduledDate)")
}
}
func perform() async throws -> some IntentResult & ProvidesDialog {
TaskService.shared.schedule(
taskId: task.id,
date: scheduledDate,
reminder: setReminder
)
let dateFormatter = DateFormatter()
dateFormatter.dateStyle = .medium
dateFormatter.timeStyle = .short
let formattedDate = dateFormatter.string(from: scheduledDate)
return .result(
dialog: "Tarea programada para el \(formattedDate)."
)
}
}El método requestConfirmation pausa la ejecución hasta la validación del usuario, protegiendo contra acciones accidentales.
Intents en widgets interactivos
Los App Intents se integran de forma natural con WidgetKit para crear widgets interactivos en iOS 17+.
import AppIntents
import WidgetKit
// Intent optimizado para widget (ejecución rápida)
struct ToggleTaskFromWidgetIntent: AppIntent {
static var title: LocalizedStringResource = "Alternar tarea"
@Parameter(title: "ID de la tarea")
var taskID: String
init() {}
init(taskID: UUID) {
self.taskID = taskID.uuidString
}
// Sin diálogo para los widgets
func perform() async throws -> some IntentResult {
guard let uuid = UUID(uuidString: taskID) else {
return .result()
}
TaskService.shared.toggleCompletion(taskId: uuid)
// Recarga inmediata del widget
WidgetCenter.shared.reloadTimelines(ofKind: "TaskWidget")
return .result()
}
}
// Vista del widget con botón interactivo
import SwiftUI
struct TaskWidgetView: View {
let task: Task
var body: some View {
Button(intent: ToggleTaskFromWidgetIntent(taskID: task.id)) {
HStack {
Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle")
.foregroundStyle(task.isCompleted ? .green : .secondary)
Text(task.title)
.strikethrough(task.isCompleted)
}
.padding()
}
.buttonStyle(.plain)
}
}Los widgets utilizan la sintaxis Button(intent:) para conectar la interacción directamente con el App Intent, sin abrir la aplicación.
Configuración del Action Button
El Action Button del iPhone 15 Pro y posteriores puede disparar App Shortcuts directamente.
import AppIntents
// Intent optimizado para Action Button
struct QuickCaptureIntent: AppIntent {
static var title: LocalizedStringResource = "Captura rápida"
static var description = IntentDescription(
"Crea rápidamente una tarea con un título."
)
// Abre la app para entrada
static var openAppWhenRun: Bool = true
func perform() async throws -> some IntentResult & OpensIntent {
// Notificación para abrir la pantalla de captura rápida
NotificationCenter.default.post(
name: .quickCaptureTriggered,
object: nil
)
return .result(opensIntent: ShowQuickCaptureViewIntent())
}
}
// Declarar en AppShortcutsProvider para Action Button
extension TaskAppShortcutsProvider {
@AppShortcutsBuilder
static var appShortcuts: [AppShortcut] {
// ... otros shortcuts
AppShortcut(
intent: QuickCaptureIntent(),
phrases: [
"Captura rápida \(.applicationName)",
"Nota rápida \(.applicationName)"
],
shortTitle: "Capturar",
systemImageName: "bolt.circle"
)
}
}Los usuarios pueden configurar el Action Button para disparar este shortcut desde Ajustes > Botón de acción.
iOS 18 introduce los macros @DeferredProperty y @ComputedProperty para reducir el boilerplate. Los App Intents también pueden residir en Swift Packages para reutilización multiplataforma.
Intents en Swift Packages
Los App Intents pueden definirse en Swift Packages para compartirlos entre iOS, macOS y watchOS.
// swift-tools-version: 5.9
import PackageDescription
let package = Package(
name: "TaskIntents",
platforms: [
.iOS(.v17),
.macOS(.v14),
.watchOS(.v10)
],
products: [
.library(name: "TaskIntents", targets: ["TaskIntents"])
],
dependencies: [],
targets: [
.target(
name: "TaskIntents",
dependencies: []
)
]
)
// Sources/TaskIntents/SharedIntents.swift
import AppIntents
// Intent compartido multiplataforma
public struct SharedCreateTaskIntent: AppIntent {
public static var title: LocalizedStringResource = "Crear una tarea"
@Parameter(title: "Título")
public var taskTitle: String
public init() {}
public func perform() async throws -> some IntentResult {
// Lógica compartida
await TaskRepository.shared.create(title: taskTitle)
return .result()
}
}
// Extensión específica de plataforma en la app
#if os(iOS)
extension SharedCreateTaskIntent {
// Comportamiento específico iOS
static var openAppWhenRun: Bool = false
}
#endifEsta arquitectura mantiene un código único para los intents adaptando el comportamiento por plataforma.
Probar los App Intents
Los App Intents pueden testearse de forma unitaria como cualquier código Swift.
import XCTest
import AppIntents
@testable import TaskApp
final class TaskIntentTests: XCTestCase {
override func setUp() {
super.setUp()
// Resetear el servicio para tests aislados
TaskService.shared.reset()
}
func testCreateTaskIntent() async throws {
// Given
var intent = CreateTaskIntent()
intent.taskTitle = "Tarea de prueba"
intent.priority = .high
// When
let result = try await intent.perform()
// Then
let tasks = TaskService.shared.fetchTasks()
XCTAssertEqual(tasks.count, 1)
XCTAssertEqual(tasks.first?.title, "Tarea de prueba")
XCTAssertEqual(tasks.first?.priority, .high)
}
func testCompleteTaskIntent() async throws {
// Given
let task = TaskService.shared.createTask(
title: "Tarea por completar",
priority: .medium
)
var intent = CompleteTaskIntent()
intent.task = TaskEntity(task: task)
// When
_ = try await intent.perform()
// Then
let updatedTask = TaskService.shared.fetchTask(id: task.id)
XCTAssertTrue(updatedTask?.isCompleted ?? false)
}
func testEntityQuery() async throws {
// Given
let task1 = TaskService.shared.createTask(title: "Tarea 1", priority: .low)
let task2 = TaskService.shared.createTask(title: "Tarea 2", priority: .high)
let query = TaskEntityQuery()
// When
let entities = try await query.entities(for: [task1.id, task2.id])
// Then
XCTAssertEqual(entities.count, 2)
}
}Los tests verifican el comportamiento de los intents independientemente de la interfaz del sistema.
Buenas prácticas y optimizaciones
Varios patrones aseguran App Intents performantes y una experiencia óptima para el usuario.
import AppIntents
// 1. Frases localizadas
struct LocalizedTaskIntent: AppIntent {
static var title: LocalizedStringResource = "task.create.title"
static var description = IntentDescription(
"task.create.description",
categoryName: "task.category"
)
@Parameter(title: "task.parameter.title")
var taskTitle: String
func perform() async throws -> some IntentResult {
// ...
return .result()
}
}
// 2. Manejo correcto de errores
enum TaskIntentError: Error, CustomLocalizedStringResourceConvertible {
case taskNotFound
case invalidInput
case serviceUnavailable
var localizedStringResource: LocalizedStringResource {
switch self {
case .taskNotFound:
return "Tarea no encontrada."
case .invalidInput:
return "Datos inválidos."
case .serviceUnavailable:
return "Servicio temporalmente no disponible."
}
}
}
struct RobustTaskIntent: AppIntent {
static var title: LocalizedStringResource = "Intent robusto"
@Parameter(title: "ID")
var taskId: String
func perform() async throws -> some IntentResult & ProvidesDialog {
guard let uuid = UUID(uuidString: taskId) else {
throw TaskIntentError.invalidInput
}
guard let task = TaskService.shared.fetchTask(id: uuid) else {
throw TaskIntentError.taskNotFound
}
return .result(dialog: "Tarea encontrada: \(task.title)")
}
}
// 3. Optimización de EntityQuery
struct OptimizedTaskQuery: EntityStringQuery {
// Búsqueda textual optimizada
func entities(matching string: String) async throws -> [TaskEntity] {
// Búsqueda en el servicio con límite
TaskService.shared.search(query: string, limit: 10)
.map { TaskEntity(task: $0) }
}
// Sugerencias limitadas para rendimiento
func suggestedEntities() async throws -> [TaskEntity] {
TaskService.shared.fetchRecentTasks(limit: 5)
.map { TaskEntity(task: $0) }
}
}
// 4. Focus Filter para los modos de Concentración
struct TaskFocusFilter: SetFocusFilterIntent {
static var title: LocalizedStringResource = "Filtrar tareas"
@Parameter(title: "Mostrar solo prioridad alta")
var showHighPriorityOnly: Bool
func perform() async throws -> some IntentResult {
TaskService.shared.setFocusFilter(highPriorityOnly: showHighPriorityOnly)
return .result()
}
}Estos patrones aseguran una integración fluida con el sistema preservando un rendimiento óptimo.
Conclusión
Los App Intents y Siri Shortcuts transforman la manera en que los usuarios interactúan con las aplicaciones iOS. En 2026, con Apple Intelligence, exponer intents ya no es opcional sino esencial para ofrecer una experiencia moderna e intuitiva.
Checklist App Intents iOS 18+
- ✅ Crear AppIntents para las acciones principales de la app
- ✅ Definir AppEntities para los datos manipulables
- ✅ Usar AppEnum para los tipos enumerados
- ✅ Implementar AppShortcutsProvider con frases de voz
- ✅ Respetar el límite máximo de 10 App Shortcuts
- ✅ Incluir
\(.applicationName)en todas las frases - ✅ Configurar ParameterSummary para feedback Siri
- ✅ Integrar dominios Apple Intelligence si aplica
- ✅ Probar los intents con tests unitarios
- ✅ Localizar títulos y descripciones
¡Empieza a practicar!
Pon a prueba tu conocimiento con nuestros simuladores de entrevista y tests técnicos.
Etiquetas
Compartir
Artículos relacionados

WidgetKit iOS 17+: Widgets Interactivos con App Intents
Guía completa para crear widgets iOS interactivos con WidgetKit y App Intents. Botones, toggles, animaciones y mejores prácticas para iOS 17+ en 2026.

Combine vs async/await en Swift: Patrones de Migración Progresiva
Guía completa para migrar de Combine a async/await en Swift: estrategias progresivas, patrones de puente y coexistencia de paradigmas en bases de código iOS.

Preguntas de entrevista sobre accesibilidad iOS en 2026: VoiceOver y Dynamic Type
Prepárate para entrevistas iOS con preguntas clave de accesibilidad: VoiceOver, Dynamic Type, traits semánticos y auditorías.