Actualizado: 15 de septiembre de 2026
App Intents es el framework declarativo de Apple que permite exponer las acciones y los datos de tu app a Siri, Spotlight, atajos, la pantalla de bloqueo, controles del Centro de Control y, desde iOS 26, a Apple Intelligence, todo con un mismo tipo Swift. Honestamente, cuando porté el mismo intent a iPhone, iPad, Mac Catalyst y visionOS me llevé varias sorpresas, y esta guía práctica recoge lo que cambia en cada plataforma, cómo funcionan los nuevos snippets interactivos y qué pasa cuando el usuario no tiene Apple Intelligence activado.
¿Qué son los App Intents y qué cambió en iOS 26?
Los App Intents son tipos Swift que describen una acción o consulta que tu app puede ejecutar en nombre del usuario, sin que este tenga que abrirla. Cada intent conforma el protocolo AppIntent, declara sus parámetros con @Parameter y devuelve un IntentResult. El sistema los muestra en Siri, Spotlight, la app Atajos, la pantalla de bloqueo, los widgets interactivos, los Control Widgets y, desde iOS 18, en la ventana de Apple Intelligence.
En iOS 26 el framework recibió varias adiciones concretas: SnippetIntent permite devolver una vista SwiftUI como respuesta enriquecida dentro de Siri; IntentDialog gana la variante .appIntelligence que reformula la respuesta con modelo generativo cuando el usuario tiene Apple Intelligence activo; AppEntity puede exponer SemanticContentDescriptor, que el sistema usa para razonar sobre el contenido sin cargarlo entero; y los Assistant Schemas, que en iOS 18 estaban limitados a dominios como Correo o Fotos, se abren a apps de terceros de las categorías Diario, Salud y Productividad.
Si vienes de @Observable y el Observation Framework, la mentalidad es similar: describes intención (tipos con macros) y el sistema se encarga del cableado. La diferencia es el consumidor: aquí no es SwiftUI, es Siri, Spotlight y el modelo de lenguaje de Apple Intelligence.
Tu primer App Intent en Swift paso a paso
Vamos a construir un intent que añada una nota rápida a una app hipotética de gestión de tareas. Sirve como esqueleto para cualquier AppIntent que ejecute una acción y devuelva un resultado en texto. Añádelo a un target compartido entre la app y su extensión de widgets. App Intents se descubren automáticamente en todos los binarios del bundle principal.
import AppIntents
import SwiftUI
struct AddQuickNoteIntent: AppIntent {
static var title: LocalizedStringResource = "Añadir nota rápida"
static var description = IntentDescription(
"Guarda una nota corta en tu bandeja de entrada.",
categoryName: "Notas"
)
// Aparece en Siri, Atajos y Spotlight con este texto.
static var openAppWhenRun: Bool = false
@Parameter(title: "Texto de la nota", requestValueDialog: "¿Qué quieres anotar?")
var body: String
@Dependency
private var store: NoteStore
func perform() async throws -> some IntentResult & ProvidesDialog {
let saved = try await store.append(body: body)
return .result(
dialog: IntentDialog(
full: "He guardado la nota: \(saved.body)",
supporting: "Nota guardada"
)
)
}
}
Puntos que suelo verificar en revisión de código: (1) title es LocalizedStringResource — no es una String normal, así que la extracción de .xcstrings lo recoge automáticamente; (2) openAppWhenRun = false permite ejecutar la acción en background sin sacar al usuario del contexto en el que estaba; (3) @Dependency inyecta un servicio registrado con AppDependencyManager.shared.add, evitando singletons colgados del AppDelegate.
Parámetros, entidades y consultas (AppEntity y AppEntityQuery)
Los parámetros primitivos (String, Int, Date, URL, IntentFile) sirven para el 70 % de los casos, pero cuando el usuario debe elegir "una tarea", "un contacto" o "una nota" concreta, hace falta modelar el dominio como AppEntity. Una entidad es la representación pública de un modelo de tu app, junto con una consulta que Siri usa para buscarla por nombre o identificador.
import AppIntents
struct TaskEntity: AppEntity {
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Tarea"
static var defaultQuery = TaskQuery()
let id: UUID
var title: String
var isDone: Bool
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(
title: "\(title)",
subtitle: isDone ? "Completada" : "Pendiente",
image: .init(systemName: isDone ? "checkmark.circle.fill" : "circle")
)
}
}
struct TaskQuery: EntityQuery {
@Dependency var store: TaskStore
func entities(for identifiers: [TaskEntity.ID]) async throws -> [TaskEntity] {
try await store.tasks(matching: identifiers)
}
func suggestedEntities() async throws -> [TaskEntity] {
try await store.recentTasks(limit: 8)
}
func entities(matching string: String) async throws -> [TaskEntity] {
try await store.search(query: string, limit: 20)
}
}
suggestedEntities() es la lista que Siri muestra cuando el usuario dice "marca como hecha" sin especificar cuál; entities(matching:) es lo que se ejecuta cuando escribe el nombre en Atajos. Una consulta bien implementada mejora drásticamente la tasa de acierto de la desambiguación por voz.
En iOS 26 podemos añadir SemanticContentDescriptor al displayRepresentation, que el modelo de Apple Intelligence usa para razonar sin necesidad de cargar toda la entidad. Es opcional pero mejora la calidad de las respuestas cuando el usuario pregunta cosas como "¿qué tareas tengo hoy relacionadas con el proyecto Aurora?".
AppShortcutsProvider: exponer intents a Siri y Spotlight
Un AppIntent aislado no aparece en Siri por sí solo. El usuario tendría que crearlo manualmente en Atajos. Para exponerlo automáticamente, se registra en un AppShortcutsProvider, que es el equivalente a un manifest declarativo de todo lo que tu app ofrece al sistema.
import AppIntents
struct QuickNotesShortcuts: AppShortcutsProvider {
static var shortcutTileColor: ShortcutTileColor = .teal
@AppShortcutsBuilder
static var appShortcuts: [AppShortcut] {
AppShortcut(
intent: AddQuickNoteIntent(),
phrases: [
"Añade una nota en \(.applicationName)",
"Anota \(\.$body) en \(.applicationName)"
],
shortTitle: "Añadir nota",
systemImageName: "square.and.pencil"
)
AppShortcut(
intent: ShowRecentNotesIntent(),
phrases: ["Muéstrame mis notas en \(.applicationName)"],
shortTitle: "Ver notas",
systemImageName: "list.bullet"
)
}
}
Las frases pueden interpolar parámetros con \(\.$parametro), y Siri las reconocerá por voz sin que el usuario tenga que crear un atajo. Puedes registrar hasta 10 App Shortcuts por app y cada uno admite un máximo de 100 frases traducidas via AppShortcuts.xcstrings.
Spotlight indexa automáticamente los App Shortcuts registrados: si el usuario escribe "añadir nota" en la búsqueda del iPhone, tu acción aparece sin código adicional. En Mac Catalyst y macOS, además, se listan en el panel Servicios y en el Centro de Comandos con el atajo global asignado por el usuario.
Cómo integrar App Intents con Apple Intelligence
Apple Intelligence usa dos rutas para invocar tu app: los App Shortcuts que ya has registrado (rápido, determinista, ideal para acciones frecuentes) y los Assistant Schemas (razonamiento generativo, ideal para consultas abiertas). Un Assistant Schema es un intent que conforma un protocolo del dominio específico (por ejemplo ShowInAppSearchResultsIntent), que el modelo entiende semánticamente.
import AppIntents
@AssistantIntent(schema: .system.search)
struct SearchTasksAssistantIntent: AppIntent {
static var title: LocalizedStringResource = "Buscar tareas"
@Parameter(title: "Consulta")
var criteria: StringSearchCriteria
@Dependency var store: TaskStore
func perform() async throws -> some IntentResult & ReturnsValue<[TaskEntity]> {
let results = try await store.search(
query: criteria.term,
limit: 20
)
return .result(value: results)
}
}
Con el macro @AssistantIntent, Apple Intelligence sabe que tu app puede "buscar cosas" y decide cuándo invocarla en función de la consulta del usuario. En iOS 26 los esquemas disponibles incluyen system.search, photos.search, journal.create, mail.reply, calendar.createEvent y varios más. La lista completa está en la documentación oficial de Assistant Schemas de App Intents.
Snippets interactivos: vistas SwiftUI dentro de Siri
La novedad más visible de iOS 26 son los Interactive Snippets: en lugar de responder con un texto, tu intent puede devolver una SnippetView — una vista SwiftUI que se renderiza dentro de la burbuja de Siri o en la ventana de Apple Intelligence. Los botones de la vista pueden ejecutar otros intents, encadenando conversaciones sin abrir la app.
import AppIntents
import SwiftUI
struct ShowRecentNotesIntent: SnippetIntent {
static var title: LocalizedStringResource = "Notas recientes"
@Dependency var store: NoteStore
func perform() async throws -> some IntentResult & ShowsSnippetView {
let notes = try await store.recent(limit: 5)
return .result(view: RecentNotesSnippet(notes: notes))
}
}
struct RecentNotesSnippet: View {
let notes: [Note]
var body: some View {
VStack(alignment: .leading, spacing: 12) {
Text("Tus notas recientes")
.font(.headline)
ForEach(notes) { note in
HStack {
Text(note.body)
.lineLimit(2)
Spacer()
Button(intent: MarkNoteDoneIntent(note: note)) {
Image(systemName: "checkmark.circle")
}
}
}
}
.padding()
}
}
Button(intent:) (el mismo que se usa en widgets interactivos desde iOS 17) ejecuta el intent dentro del proceso del snippet, sin abrir la app. La vista se re-renderiza automáticamente si el intent devuelve un SnippetIntent, lo que permite construir pequeñas experiencias multi-paso (elegir una tarea → marcarla → mostrar la siguiente).
El snippet vive en un contenedor con altura máxima limitada (≈ 400 pt en iPhone, ≈ 560 pt en iPad), sin gestos de scroll horizontal ni NavigationStack. Si necesitas más, ofrece un botón "Abrir en la app" con OpenAppIntent.
Control Widgets, Focus Filters y Live Activities
App Intents es también el motor detrás de otras superficies fuera de Siri:
- Control Widgets (iOS 18+): controles pequeños en el Centro de Control y la pantalla de bloqueo. Cada control se asocia a un
ControlValueProviderque devuelve unAppIntent. En watchOS 26 los Control no están soportados; usa complicaciones conWidgetKiten su lugar. - Focus Filters: si conformas
SetFocusFilterIntent, tu app se puede configurar por modo de concentración (Trabajo oculta los correos personales, por ejemplo). - Live Activities: los botones dentro del
ActivityAttributes.ContentStateaceptanButton(intent:), ejecutando App Intents sin salir de la Dynamic Island. - Widgets interactivos: los toggles y botones de un widget son
AppIntentque se ejecutan en background, y el mismo tipo se puede reutilizar desde Siri.
Esta reutilización es la razón por la que insisto en modelar cada acción como un AppIntent desde el principio: escribes una vez y consumes desde cinco superficies distintas. Si vienes de la era de Intents.framework con archivos .intentdefinition, el ahorro es enorme.
Diferencias entre iPhone, iPad, Mac Catalyst y visionOS
El código de AppIntent compila igual en las cinco plataformas actuales de Apple, pero cada una expone superficies distintas. Esta tabla resume lo que uso como referencia cuando porto una app entre plataformas.
| Superficie | iOS 26 | iPadOS 26 | macOS 26 | visionOS 26 | watchOS 26 |
|---|---|---|---|---|---|
| Siri + Atajos | Sí | Sí | Sí | Sí | Sí |
| Spotlight indexado | Sí | Sí | Sí | Sí | No |
| Widgets interactivos | Sí | Sí | Sí | Sí | Limitado |
| Control Widgets | Sí | Sí | Sí | No | No |
| Snippets interactivos | Sí | Sí | Sí | Sí | No |
| Apple Intelligence | iPhone 15 Pro+ | M1+ | Silicon | Sí | No |
| Focus Filters | Sí | Sí | Sí | Sí | Hereda del iPhone |
Una nota específica de Mac Catalyst: los App Shortcuts aparecen en el menú Servicios y aceptan atajos globales de teclado que el usuario configura en Ajustes del sistema; en cambio, los SnippetIntent se renderizan en la ventana de Siri con el tema del sistema (claro/oscuro) y respetan ScenePhase. En visionOS, los snippets se anclan al espacio donde se invocó a Siri, no al de la app; hay que evitar usar DragGesture o gestos táctiles complejos porque solo los eye + pinch están garantizados.
Cómo probar App Intents en el simulador
La mayor sorpresa para gente que viene de SiriKit es que sí se pueden probar los intents en el simulador, y muy cómodamente. Estas son las tres formas que uso a diario:
- App Atajos: abre la app Atajos en el simulador, tus
AppShortcutaparecen en "Sugerencias del sistema" y puedes tocar para ejecutarlos. Rápido para depurar el flujo deperform(). - Siri en el simulador: desde iOS 17.4 se puede invocar Siri con
xcrun simctl siri booted "Añade una nota que diga Comprar leche". Útil para pruebas de reconocimiento de frase. - Test unitarios: instancia el intent y llama a
perform()en unXCTest. Es unasync throwsnormal, así que puedes verificar elIntentResultdevuelto. Para más patrones de testing consulta nuestra guía de Swift Testing en Xcode 26.
import Testing
import AppIntents
@testable import QuickNotes
@Test("AddQuickNoteIntent guarda la nota y devuelve diálogo")
func testAddQuickNoteIntent() async throws {
let store = InMemoryNoteStore()
AppDependencyManager.shared.add(dependency: store)
var intent = AddQuickNoteIntent()
intent.body = "Comprar leche"
let result = try await intent.perform()
#expect(store.all.count == 1)
#expect(store.all.first?.body == "Comprar leche")
}
Errores comunes y cómo solucionarlos
Estos son los tropiezos que me han costado tiempo real y que veo repetirse en pull requests de gente que llega nueva al framework:
- "No pude entenderte" en Siri: normalmente falta el
AppShortcutsProvidero el intent está en un target que no forma parte del bundle principal. App Intents se descubren por reflection al arrancar la app; si el binario no se linka, Siri no lo ve. - Cambios en
phrasesque no aplican: el sistema cachea los App Shortcuts por versión de build. Incrementa el build number o borra la app del simulador para forzar reindexación. - Crash al invocar desde widget: típicamente es un
@Dependencyno registrado. La extensión de widget corre en un proceso distinto y necesita su propioAppDependencyManager.shared.adden el@maindel widget. - Localización perdida:
LocalizedStringResourcerequiere que el.xcstringsesté en el mismo bundle que el intent; si tu intent vive en un Swift Package, pasa elbundle:explícitamente. - Snippet vacío en visionOS: las vistas SwiftUI del snippet no pueden usar
@Environment(\.dismiss)niNavigationStack; el compilador no avisa, pero el snippet aparece en blanco. Diseña con contenedores planos.
Si estás modernizando la persistencia de la app en el mismo esfuerzo, mira también nuestra guía de SwiftData para producción en iOS 26 — combinar App Intents con @Query es especialmente cómodo para exponer entidades a Siri sin duplicar el modelo.
Preguntas frecuentes
¿Cuál es la diferencia entre App Intents y SiriKit?
SiriKit (2016) usa dominios predefinidos (mensajería, pagos, workouts) y archivos .intentdefinition con clases INExtension; App Intents (2022) es puro Swift, admite cualquier dominio y unifica Siri, Spotlight, widgets, atajos y Apple Intelligence en un único tipo. Apple recomienda App Intents para todas las apps nuevas y para migrar las existentes.
¿App Intents funciona sin Apple Intelligence?
Sí. Solo los @AssistantIntent requieren Apple Intelligence para su ruta generativa; los AppIntent y AppShortcut normales funcionan en cualquier iPhone/iPad compatible con iOS 16 o superior, y en Mac, Apple Watch, Apple TV y Vision Pro. Sin Apple Intelligence, un @AssistantIntent se degrada a un AppIntent normal disponible en Siri y Atajos.
¿Cómo pruebo un App Intent en el simulador sin decir la frase en voz alta?
Usa xcrun simctl siri booted "tu frase" desde la terminal (iOS 17.4+), o abre la app Atajos dentro del simulador y ejecuta el App Shortcut manualmente. También puedes instanciar el AppIntent en un test y llamar directamente a perform(), que es async throws.
¿Cuántos App Shortcuts puedo registrar por app?
El límite documentado en iOS 26 es de 10 App Shortcuts por app, cada uno con hasta 100 frases traducidas via AppShortcuts.xcstrings. Si tu app necesita más acciones expuestas, prioriza las más frecuentes en el AppShortcutsProvider y deja el resto disponibles solo desde Atajos como AppIntent públicos.
¿Los Snippets interactivos funcionan en widgets?
No exactamente. Los SnippetIntent se muestran en la ventana de Siri y Apple Intelligence, no en la pantalla de inicio. Los widgets interactivos usan Button(intent:) y Toggle(isOn:intent:) directamente dentro de la vista del widget, sin envolverlos en un SnippetIntent. Puedes compartir el mismo AppIntent subyacente entre widget y snippet.
¿App Intents está disponible en watchOS?
Sí, desde watchOS 10. En watchOS 26 se soportan intents ejecutados por Siri y la app Atajos, pero no hay Control Widgets ni Spotlight indexado. Los Snippets interactivos tampoco se renderizan en el reloj; si el usuario invoca un SnippetIntent desde el watch, se envía al iPhone emparejado.




