App Intents 2.0 y Siri Shortcuts: guía de automatización iOS 27
Guía completa de App Intents 2.0 y Siri Shortcuts para iOS 27. Crear streaming responses, conversaciones multi-turno, View Annotations e integrar con Foundation Models.

App Intents 2.0 y Siri Shortcuts representan la única vía para que las apps de terceros se integren con Siri en iOS 27. Tras WWDC 2026, Apple deprecó SiriKit y posicionó App Intents como el framework obligatorio para la interacción por voz, descubrimiento en Spotlight y flujos de automatización.
Este artículo presenta la creación completa de App Intents y Siri Shortcuts para iOS 27, desde los conceptos fundamentales hasta streaming responses, conversaciones multi-turno y la API View Annotations.
Comprender el framework App Intents 2.0
El framework App Intents, introducido con iOS 16 y expandido a la versión 2.0 en WWDC 2026, es un framework Swift-nativo y declarativo para construir acciones detectables por el sistema. iOS 27 incorpora cuatro novedades principales: streaming responses para operaciones de larga duración, seguimientos conversacionales multi-turno, View Annotations para referenciar elementos en pantalla, y App Schemas para comprensión semántica sin frases de entrenamiento.
En WWDC 2026, Apple deprecó formalmente SiriKit y estableció App Intents como la única forma en que Siri puede interactuar con apps de terceros. El código SiriKit existente continúa compilando con advertencias de deprecación, pero Apple ha señalado una ventana de soporte de dos a tres años antes de su eliminación.
import AppIntents
// An AppIntent represents an action users can perform
struct CreateTaskIntent: AppIntent {
// Title displayed in Shortcuts and Siri
static var title: LocalizedStringResource = "Create a task"
// Description for accessibility and suggestions
static var description = IntentDescription(
"Creates a new task in the application."
)
// Parameter with automatic validation
@Parameter(title: "Task title")
var taskTitle: String
// Optional parameter with default value
@Parameter(title: "Priority", default: .medium)
var priority: TaskPriority
// Action execution with async/await
func perform() async throws -> some IntentResult & ReturnsValue<TaskEntity> {
// Create task via service
let task = TaskService.shared.createTask(
title: taskTitle,
priority: priority
)
// Return created entity for chaining
return .result(value: TaskEntity(task: task))
}
}El intent declara sus parámetros mediante el property wrapper @Parameter, permitiendo a Siri solicitar los valores faltantes conversacionalmente. El método perform() ejecuta la lógica de negocio y retorna un resultado tipado. El sistema de tipos sirve como el schema que Siri AI descubre dinámicamente.
Streaming Responses para acciones de larga duración
App Intents 2.0 introduce streaming responses, permitiendo a los intents reportar progreso durante la ejecución en lugar de bloquearse hasta completar. Esta capacidad aborda escenarios como subida de archivos, sincronización de datos o cálculos complejos donde el usuario necesita feedback.
import AppIntents
struct ExportDataIntent: AppIntent {
static var title: LocalizedStringResource = "Export data"
@Parameter(title: "Format")
var format: ExportFormat
// Streaming response for progress reporting
func perform() async throws -> some IntentResult & ProvidesDialog {
let totalItems = DataService.shared.itemCount
var processed = 0
// Stream progress updates to Siri
for item in DataService.shared.allItems {
await exportItem(item, format: format)
processed += 1
// Report progress at intervals
if processed % 100 == 0 {
await reportProgress(
"Exported (processed) of (totalItems) items..."
)
}
}
return .result(
dialog: "Export complete. (totalItems) items saved as (format.rawValue)."
)
}
}Las streaming responses permiten feedback natural durante operaciones que toman más de unos segundos, manteniendo el engagement del usuario en lugar de dejarlo esperando en silencio.
Seguimientos conversacionales multi-turno
Las conversaciones multi-turno permiten a Siri hacer preguntas clarificadoras y continuar la interacción dentro de una sola sesión. Esta característica transforma los App Intents de comandos de un solo disparo a flujos de trabajo conversacionales.
import AppIntents
struct SmartTaskIntent: AppIntent {
static var title: LocalizedStringResource = "Create smart task"
@Parameter(title: "Task title")
var taskTitle: String?
@Parameter(title: "Priority")
var priority: TaskPriority?
@Parameter(title: "Due date")
var dueDate: Date?
// Multi-turn conversation flow
func perform() async throws -> some IntentResult & ProvidesDialog {
// Request missing parameters conversationally
let title = try await taskTitle ?? requestValue(
for: .$taskTitle,
dialog: "What should the task be called?"
)
let taskPriority = try await priority ?? requestValue(
for: .$priority,
dialog: "What priority level?"
)
// Optional follow-up
let date: Date?
if try await requestConfirmation(
result: .result(dialog: "Should this task have a due date?")
) {
date = try await requestValue(
for: .$dueDate,
dialog: "When is it due?"
)
} else {
date = nil
}
let task = TaskService.shared.createTask(
title: title,
priority: taskPriority,
dueDate: date
)
return .result(
dialog: "Created '(task.title)' with (taskPriority.rawValue) priority."
)
}
}El método requestValue(for:dialog:) pausa la ejecución hasta que Siri recibe la respuesta del usuario, creando un intercambio natural de ida y vuelta en lugar de requerir todos los parámetros por adelantado.
API View Annotations para referencias en pantalla
La API View Annotations, nueva en iOS 27, permite a los usuarios referenciar elementos de UI directamente en comandos de Siri usando frases como "esta foto", "el tercero" o "ese mensaje". Esta capacidad requiere anotar vistas SwiftUI con información semántica.
import SwiftUI
import AppIntents
struct PhotoGridView: View {
let photos: [Photo]
var body: some View {
LazyVGrid(columns: [GridItem(.adaptive(minimum: 100))]) {
ForEach(photos) { photo in
PhotoThumbnail(photo: photo)
// Annotate view for Siri reference
.appIntentAnnotation(
entity: PhotoEntity(photo: photo),
label: photo.title
)
}
}
}
}
// Intent that accepts view-referenced entities
struct SharePhotoIntent: AppIntent {
static var title: LocalizedStringResource = "Share photo"
// Parameter resolved from on-screen annotation
@Parameter(title: "Photo", supportsViewAnnotation: true)
var photo: PhotoEntity
func perform() async throws -> some IntentResult & ProvidesDialog {
await ShareService.share(photo.id)
return .result(dialog: "Photo shared.")
}
}Cuando un usuario dice "comparte esta foto" mientras ve la cuadrícula, Siri resuelve "esta" a la PhotoEntity actualmente enfocada o tocada por última vez a través de la anotación. Esto elimina la fricción de describir elementos verbalmente.
View Annotations requiere iOS 27, pero la capa App Intents funciona en todos los dispositivos iOS 27 incluyendo iPhone 11 y posteriores. Las características Apple Intelligence como la conciencia en pantalla necesitan iPhone 15 Pro o posterior.
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, buscar y manipular los datos de la app. Construir entidades efectivas es esencial para los patrones de gestión de estado en SwiftUI en apps orientadas a intents.
import AppIntents
// Internal data model
struct Task: Identifiable, Codable {
let id: UUID
var title: String
var priority: TaskPriority
var isCompleted: Bool
var dueDate: Date?
}
// Entity exposed to the system
struct TaskEntity: AppEntity {
// Required unique identifier
var id: UUID
// Displayable properties
var title: String
var priority: TaskPriority
var isCompleted: Bool
// Display configuration in the system
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Task"
// Visual representation of the instance
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(
title: "(title)",
subtitle: "(priority.rawValue)",
image: .init(systemName: isCompleted ? "checkmark.circle.fill" : "circle")
)
}
// Default query for searching entities
static var defaultQuery = TaskEntityQuery()
// Initializer from internal model
init(task: Task) {
self.id = task.id
self.title = task.title
self.priority = task.priority
self.isCompleted = task.isCompleted
}
}
// Optimized entity query with search
struct TaskEntityQuery: EntityStringQuery {
// Text search with service-side filtering
func entities(matching string: String) async throws -> [TaskEntity] {
TaskService.shared.search(query: string, limit: 10)
.map { TaskEntity(task: $0) }
}
// Search by identifiers
func entities(for identifiers: [UUID]) async throws -> [TaskEntity] {
TaskService.shared.fetchTasks()
.filter { identifiers.contains($0.id) }
.map { TaskEntity(task: $0) }
}
// Limited suggestions for performance
func suggestedEntities() async throws -> [TaskEntity] {
TaskService.shared.fetchRecentTasks(limit: 5)
.map { TaskEntity(task: $0) }
}
}El protocolo EntityStringQuery añade capacidad de búsqueda textual, permitiendo a Siri encontrar entidades por nombre. El método suggestedEntities() alimenta las interfaces de Siri y Shortcuts con opciones relevantes.
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). Mezclarlos causa complejidad innecesaria.
Crear App Enums para valores fijos
Los App Enums exponen tipos enumerados al sistema, permitiendo a Siri ofrecer opciones contextuales con representaciones visuales.
import AppIntents
// Enum exposed to the system
enum TaskPriority: String, AppEnum, Codable {
case low
case medium
case high
// Type name displayed
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Priority"
// Representation of each case
static var caseDisplayRepresentations: [TaskPriority: DisplayRepresentation] = [
.low: DisplayRepresentation(
title: "Low",
image: .init(systemName: "arrow.down.circle")
),
.medium: DisplayRepresentation(
title: "Medium",
image: .init(systemName: "minus.circle")
),
.high: DisplayRepresentation(
title: "High",
image: .init(systemName: "exclamationmark.circle")
)
]
}Los iconos SF Symbols enriquecen la visualización en Shortcuts y las sugerencias de Siri, haciendo la selección más rápida que las descripciones verbales.
¿Listo para aprobar tus entrevistas de iOS?
Practica con nuestros simuladores interactivos, flashcards y tests técnicos.
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, el Action Button y los nuevos flujos agénticos de Siri AI.
import AppIntents
// Provider declaring all app shortcuts
struct TaskAppShortcutsProvider: AppShortcutsProvider {
// Maximum 10 shortcuts per application
@AppShortcutsBuilder
static var appShortcuts: [AppShortcut] {
// Shortcut to create a task
AppShortcut(
intent: CreateTaskIntent(),
phrases: [
// The .applicationName placeholder is REQUIRED
"Create a task with (.applicationName)",
"New task in (.applicationName)",
"Add a task to (.applicationName)"
],
shortTitle: "Create Task",
systemImageName: "plus.circle"
)
// Shortcut with dynamic parameter
AppShortcut(
intent: CompleteTaskIntent(),
phrases: [
"Complete (.$taskName) in (.applicationName)",
"Mark (.$taskName) as done with (.applicationName)"
],
shortTitle: "Complete Task",
systemImageName: "checkmark.circle"
)
// Shortcut for smart conversational intent
AppShortcut(
intent: SmartTaskIntent(),
phrases: [
"Quick task (.applicationName)",
"Remind me (.applicationName)"
],
shortTitle: "Smart Task",
systemImageName: "brain"
)
}
}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.
Intents en widgets interactivos
Los App Intents se integran con WidgetKit para crear widgets interactivos. Comprender los patrones de navegación en SwiftUI ayuda al construir transiciones de widget a app.
import AppIntents
import WidgetKit
// Widget-optimized intent (fast execution)
struct ToggleTaskFromWidgetIntent: AppIntent {
static var title: LocalizedStringResource = "Toggle task"
@Parameter(title: "Task ID")
var taskID: String
init() {}
init(taskID: UUID) {
self.taskID = taskID.uuidString
}
// No dialog for widgets
func perform() async throws -> some IntentResult {
guard let uuid = UUID(uuidString: taskID) else {
return .result()
}
TaskService.shared.toggleCompletion(taskId: uuid)
// Immediate widget refresh
WidgetCenter.shared.reloadTimelines(ofKind: "TaskWidget")
return .result()
}
}
// Widget view with interactive button
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.
Integración con Foundation Models Framework
iOS 27 introduce el Foundation Models framework, habilitando inferencia LLM en dispositivo. Aunque Foundation Models no tiene integración directa con Siri, las apps pueden usarlo dentro de App Intents para procesar lenguaje natural o generar respuestas.
import AppIntents
import FoundationModels
struct SummarizeNotesIntent: AppIntent {
static var title: LocalizedStringResource = "Summarize notes"
@Parameter(title: "Topic")
var topic: String
func perform() async throws -> some IntentResult & ProvidesDialog {
let notes = NoteService.shared.fetchNotes(matching: topic)
let content = notes.map(.content).joined(separator: "
")
// Use Foundation Models for on-device summarization
let model = LanguageModel.default
let summary = try await model.generate(
prompt: "Summarize these notes in 2 sentences: (content)",
maxTokens: 100
)
return .result(dialog: IntentDialog(summary))
}
}Foundation Models ejecuta aproximadamente 30 tokens por segundo en dispositivo sin costo por solicitud. El framework requiere iPhone 15 Pro o iPhone 16 y posterior para procesamiento en dispositivo.
Migrar de SiriKit a App Intents
Para apps que aún usan SiriKit, la migración sigue un proceso estructurado. Xcode 27 proporciona una herramienta "Convert to App Intent" para configuraciones de Widget, pero los handlers personalizados requieren reescritura manual.
// Before: SiriKit Intent Handler
class CreateTaskIntentHandler: NSObject, CreateTaskIntentHandling {
func handle(intent: CreateTaskIntent) async -> CreateTaskIntentResponse {
guard let title = intent.taskTitle else {
return CreateTaskIntentResponse(code: .failure, userActivity: nil)
}
let task = TaskService.shared.createTask(title: title)
return CreateTaskIntentResponse.success(task: task)
}
}
// After: App Intent (iOS 27)
struct CreateTaskIntent: AppIntent {
static var title: LocalizedStringResource = "Create a task"
@Parameter(title: "Task title")
var taskTitle: String
func perform() async throws -> some IntentResult & ReturnsValue<TaskEntity> {
let task = TaskService.shared.createTask(title: taskTitle)
return .result(value: TaskEntity(task: task))
}
}La migración elimina los archivos .intentdefinition y los targets Intent Extension. Toda la lógica de intents se mueve al target principal de la app como structs Swift.
Probar los App Intents
El framework AppIntentsTesting valida intents a través de la infraestructura real de Siri, Shortcuts y Spotlight sin mocks.
import XCTest
import AppIntentsTesting
@testable import TaskApp
final class TaskIntentTests: XCTestCase {
override func setUp() {
super.setUp()
TaskService.shared.reset()
}
func testCreateTaskIntent() async throws {
// Given
var intent = CreateTaskIntent()
intent.taskTitle = "Test Task"
intent.priority = .high
// When
let result = try await intent.perform()
// Then
let tasks = TaskService.shared.fetchTasks()
XCTAssertEqual(tasks.count, 1)
XCTAssertEqual(tasks.first?.title, "Test Task")
XCTAssertEqual(tasks.first?.priority, .high)
}
func testEntityQuery() async throws {
// Given
let task1 = TaskService.shared.createTask(title: "Task 1", priority: .low)
let task2 = TaskService.shared.createTask(title: "Task 2", priority: .high)
let query = TaskEntityQuery()
// When
let entities = try await query.entities(for: [task1.id, task2.id])
// Then
XCTAssertEqual(entities.count, 2)
}
func testStreamingIntent() async throws {
// Given
var intent = ExportDataIntent()
intent.format = .json
// When
let result = try await intent.perform()
// Then: verify completion dialog
XCTAssertNotNil(result)
}
}Los tests verifican el comportamiento de los intents independientemente de la interfaz del sistema, detectando regresiones antes del despliegue.
Sources
- Apple WWDC 2026 Announcements : App Intents 2.0 and SiriKit deprecation
- Swift.org Blog : Swift 6.3 and 6.4 releases
- Apple Developer News : iOS 27 SDK requirements and Foundation Models framework
- iOS 27 App Intents Developer Guide : Streaming responses and View Annotations details
Checklist de implementación App Intents iOS 27
Para apps que apuntan a iOS 27, los App Intents ya no son opcionales. Siri AI compone acciones de múltiples pasos a través de apps, y las apps sin intents publicados quedan excluidas de estos flujos agénticos.
- 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 - Añadir View Annotations a las vistas SwiftUI para referencias en pantalla
- Implementar streaming responses para operaciones de larga duración
- Soportar conversaciones multi-turno para flujos de trabajo complejos
- Migrar de SiriKit antes de que cierre la ventana de deprecación
- Probar intents con el framework AppIntentsTesting
- Localizar títulos y descripciones
¡Empieza a practicar!
Pon a prueba tu conocimiento con nuestros simuladores de entrevista y tests técnicos.
¿Sabrías detectar el bug en iOS?
Un fragmento real, un bug oculto, un intento al día. Sin cuenta para probar.

Escrito por
Anthony Fillion-MailletFundador de SharpSkill
Desarrollador fullstack desde hace más de 10 años. Dirige SharpSkill y responde por todo lo que se publica aquí.
Actualizado el 20 de agosto de 2026
Etiquetas
Compartir
Artículos relacionados

WidgetKit iOS 17-26: Widgets Interactivos con App Intents
Guía completa para crear widgets iOS interactivos con WidgetKit y App Intents. Botones, toggles, animaciones, estilo Liquid Glass de iOS 26 y actualizaciones de WWDC 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.