App Intents e Siri Shortcuts: automação avançada de iOS 2026
Guia completo de App Intents e Siri Shortcuts para iOS 18+. Criar ações personalizadas para Siri, integrar Apple Intelligence e automatizar o app Swift em 2026.

Em 2026, com Apple Intelligence e o framework App Intents, as aplicações iOS entram numa era em que a intenção do usuário importa mais do que a interface gráfica. Apps que não expõem intents tornam-se invisíveis em um sistema operacional orientado a IA. App Intents é a fundação que permite a Siri, Spotlight, widgets e Action Button interagirem com as funcionalidades do aplicativo.
Este artigo apresenta a criação completa de App Intents e Siri Shortcuts para iOS 18+, dos conceitos fundamentais até a integração com Apple Intelligence e os App Intent Domains.
Compreender o framework App Intents
O framework App Intents, introduzido com iOS 16, moderniza a construção de intents em Swift, substituindo o antigo SiriKit Intents framework. Essa arquitetura declarativa permite criar ações detectáveis pelo sistema: Spotlight, app Atalhos, Siri e Action Button.
import AppIntents
// Um AppIntent representa uma ação que o usuário pode executar
struct CreateTaskIntent: AppIntent {
// Título exibido em Atalhos e Siri
static var title: LocalizedStringResource = "Criar uma tarefa"
// Descrição para acessibilidade e sugestões
static var description = IntentDescription(
"Cria uma nova tarefa no aplicativo."
)
// Parâmetro com validação automática
@Parameter(title: "Título da tarefa")
var taskTitle: String
// Parâmetro opcional com valor padrão
@Parameter(title: "Prioridade", default: .medium)
var priority: TaskPriority
// Execução da ação
func perform() async throws -> some IntentResult & ReturnsValue<TaskEntity> {
// Cria a tarefa via serviço
let task = TaskService.shared.createTask(
title: taskTitle,
priority: priority
)
// Retorna a entidade criada para encadeamento
return .result(value: TaskEntity(task: task))
}
}O intent declara seus parâmetros via property wrapper @Parameter, permitindo a Siri solicitar valores ausentes. O método perform() executa a lógica de negócio e retorna um resultado tipado.
Definir App Entities para os dados
As App Entities representam os "substantivos" do aplicativo: os objetos sobre os quais os intents operam. Permitem a Siri compreender e manipular os dados do app.
import AppIntents
// Modelo de dados interno
struct Task: Identifiable, Codable {
let id: UUID
var title: String
var priority: TaskPriority
var isCompleted: Bool
var dueDate: Date?
}
// Entidade exposta ao sistema
struct TaskEntity: AppEntity {
// Identificador único obrigatório
var id: UUID
// Propriedades exibíveis
var title: String
var priority: TaskPriority
var isCompleted: Bool
// Configuração de exibição no sistema
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Tarefa"
// Representação visual da instância
var displayRepresentation: DisplayRepresentation {
DisplayRepresentation(
title: "\(title)",
subtitle: "\(priority.rawValue)",
image: .init(systemName: isCompleted ? "checkmark.circle.fill" : "circle")
)
}
// Query padrão para buscar entidades
static var defaultQuery = TaskEntityQuery()
// Inicializador a partir do modelo interno
init(task: Task) {
self.id = task.id
self.title = task.title
self.priority = task.priority
self.isCompleted = task.isCompleted
}
}
// Query para buscar e filtrar entidades
struct TaskEntityQuery: EntityQuery {
// Busca por identificadores
func entities(for identifiers: [UUID]) async throws -> [TaskEntity] {
TaskService.shared.fetchTasks()
.filter { identifiers.contains($0.id) }
.map { TaskEntity(task: $0) }
}
// Sugestões exibidas na interface
func suggestedEntities() async throws -> [TaskEntity] {
TaskService.shared.fetchTasks()
.filter { !$0.isCompleted }
.prefix(5)
.map { TaskEntity(task: $0) }
}
}A EntityQuery define como o sistema busca e sugere entidades. Os métodos entities(for:) e suggestedEntities() alimentam as interfaces do Siri e dos Atalhos.
Use AppEnum para tipos com um conjunto fixo de valores (prioridade, status), e AppEntity para tipos dinâmicos criados pelo usuário (tarefas, notas, contatos).
Criar App Enums para valores fixos
Os App Enums expõem tipos enumerados ao sistema, permitindo que Siri ofereça opções contextuais.
import AppIntents
// Enum exposto ao sistema
enum TaskPriority: String, AppEnum, Codable {
case low
case medium
case high
// Nome do tipo exibido
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Prioridade"
// Representação de cada caso
static var caseDisplayRepresentations: [TaskPriority: DisplayRepresentation] = [
.low: DisplayRepresentation(
title: "Baixa",
image: .init(systemName: "arrow.down.circle")
),
.medium: DisplayRepresentation(
title: "Média",
image: .init(systemName: "minus.circle")
),
.high: DisplayRepresentation(
title: "Alta",
image: .init(systemName: "exclamationmark.circle")
)
]
}
// Enum para o status da tarefa
enum TaskStatus: String, AppEnum, Codable {
case pending
case inProgress
case completed
static var typeDisplayRepresentation: TypeDisplayRepresentation = "Status"
static var caseDisplayRepresentations: [TaskStatus: DisplayRepresentation] = [
.pending: "Pendente",
.inProgress: "Em andamento",
.completed: "Concluída"
]
}As representações visuais (ícones SF Symbols) enriquecem a exibição em Atalhos e nas sugestões da Siri.
Implementar AppShortcutsProvider
O AppShortcutsProvider expõe os App Shortcuts ao sistema, deixando-os disponíveis imediatamente sem configuração do usuário. Esses atalhos aparecem em Spotlight, Siri e Action Button.
import AppIntents
// Provider que declara todos os atalhos do app
struct TaskAppShortcutsProvider: AppShortcutsProvider {
// Máximo de 10 atalhos por aplicativo
@AppShortcutsBuilder
static var appShortcuts: [AppShortcut] {
// Atalho para criar uma tarefa
AppShortcut(
intent: CreateTaskIntent(),
phrases: [
// O placeholder .applicationName é OBRIGATÓRIO
"Criar uma tarefa com \(.applicationName)",
"Nova tarefa em \(.applicationName)",
"Adicionar uma tarefa ao \(.applicationName)"
],
shortTitle: "Criar tarefa",
systemImageName: "plus.circle"
)
// Atalho para listar tarefas
AppShortcut(
intent: ListTasksIntent(),
phrases: [
"Mostrar minhas tarefas em \(.applicationName)",
"Exibir tarefas do \(.applicationName)",
"Quais são minhas tarefas \(.applicationName)"
],
shortTitle: "Minhas tarefas",
systemImageName: "list.bullet"
)
// Atalho com parâmetro dinâmico
AppShortcut(
intent: CompleteTaskIntent(),
phrases: [
"Concluir \(\.$taskName) em \(.applicationName)",
"Marcar \(\.$taskName) como feita com \(.applicationName)"
],
shortTitle: "Concluir tarefa",
systemImageName: "checkmark.circle"
)
}
}As frases de voz devem incluir o placeholder \(.applicationName) para que Siri identifique o app de destino. Parâmetros dinâmicos como \(\.$taskName) permitem comandos contextuais.
Um aplicativo pode declarar no máximo 10 App Shortcuts. Convém priorizar as ações mais frequentes e úteis para os usuários.
Intents com parâmetros complexos
Intents podem aceitar parâmetros complexos, incluindo entidades e opções de configuração.
import AppIntents
struct CompleteTaskIntent: AppIntent {
static var title: LocalizedStringResource = "Concluir uma tarefa"
static var description = IntentDescription(
"Marca uma tarefa como concluída."
)
// Parâmetro tipo entidade com busca automática
@Parameter(title: "Tarefa")
var task: TaskEntity
// Parâmetro opcional com data
@Parameter(title: "Data de conclusão")
var completionDate: Date?
// Configuração do diálogo Siri
static var parameterSummary: some ParameterSummary {
Summary("Concluir \(\.$task)") {
\.$completionDate
}
}
func perform() async throws -> some IntentResult & ProvidesDialog {
// Atualiza a tarefa
TaskService.shared.completeTask(
id: task.id,
completionDate: completionDate ?? Date()
)
// Feedback de voz para Siri
return .result(
dialog: "A tarefa \(task.title) foi marcada como concluída."
)
}
}
// Intent retornando uma lista de entidades
struct ListTasksIntent: AppIntent {
static var title: LocalizedStringResource = "Listar tarefas"
// Filtro opcional
@Parameter(title: "Status", default: nil)
var statusFilter: TaskStatus?
@Parameter(title: "Prioridade", 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)
}
}O ParameterSummary define como Siri apresenta o intent durante a execução por voz, fornecendo um feedback natural.
Pronto para mandar bem nas entrevistas de iOS?
Pratique com nossos simuladores interativos, flashcards e testes tecnicos.
Integração com Apple Intelligence
iOS 18 introduz os App Intent Domains, coleções de APIs projetadas para funcionalidades específicas. Esses domínios permitem a Apple Intelligence compreender e executar ações com maior precisão.
import AppIntents
// Conformidade ao domínio Bookmarks para integração Apple Intelligence
struct SaveBookmarkIntent: AppIntent {
static var title: LocalizedStringResource = "Salvar um favorito"
// Parâmetro URL com validação automática
@Parameter(title: "URL")
var url: URL
@Parameter(title: "Título", default: nil)
var title: String?
@Parameter(title: "Pasta", default: nil)
var folder: BookmarkFolderEntity?
// Abrir o app se necessário
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: "Favorito salvo: \(bookmark.title)"
)
}
}
// Intent com consciência do conteúdo da tela (iOS 18.4+)
struct AnalyzeScreenContentIntent: AppIntent {
static var title: LocalizedStringResource = "Analisar conteúdo"
// Acesso ao contexto da tela via Apple Intelligence
@Parameter(title: "Contexto")
var screenContext: String?
func perform() async throws -> some IntentResult & ProvidesDialog {
guard let context = screenContext else {
return .result(dialog: "Nenhum conteúdo para analisar.")
}
// Processar conteúdo extraído pela Apple Intelligence
let analysis = ContentAnalyzer.analyze(context)
return .result(dialog: analysis.summary)
}
}Os domínios predefinidos (Books, Camera, Spreadsheets) permitem a Siri responder às solicitações com precisão graças a modelos treinados nessas tarefas específicas.
Ações Siri com confirmação do usuário
Para ações sensíveis, o sistema pode solicitar confirmação antes da execução.
import AppIntents
struct DeleteTaskIntent: AppIntent {
static var title: LocalizedStringResource = "Excluir uma tarefa"
@Parameter(title: "Tarefa")
var task: TaskEntity
// Requer confirmação do usuário
static var isDiscoverable: Bool = true
func perform() async throws -> some IntentResult & ProvidesDialog {
// Solicita confirmação via diálogo
try await requestConfirmation(
result: .result(
dialog: "Tem certeza que deseja excluir \(task.title)?"
)
)
// Exclui após confirmação
TaskService.shared.deleteTask(id: task.id)
return .result(
dialog: "A tarefa \(task.title) foi excluída."
)
}
}
// Intent com várias etapas de diálogo
struct ScheduleTaskIntent: AppIntent {
static var title: LocalizedStringResource = "Agendar uma tarefa"
@Parameter(title: "Tarefa")
var task: TaskEntity
@Parameter(title: "Data")
var scheduledDate: Date
@Parameter(title: "Lembrete", default: true)
var setReminder: Bool
static var parameterSummary: some ParameterSummary {
When(\.$setReminder, .equalTo, true) {
Summary("Agendar \(\.$task) para \(\.$scheduledDate) com lembrete")
} otherwise: {
Summary("Agendar \(\.$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: "Tarefa agendada para \(formattedDate)."
)
}
}O método requestConfirmation pausa a execução até a validação do usuário, protegendo contra ações acidentais.
Intents em widgets interativos
Os App Intents se integram naturalmente com WidgetKit para criar widgets interativos no iOS 17+.
import AppIntents
import WidgetKit
// Intent otimizado para widget (execução rápida)
struct ToggleTaskFromWidgetIntent: AppIntent {
static var title: LocalizedStringResource = "Alternar tarefa"
@Parameter(title: "ID da tarefa")
var taskID: String
init() {}
init(taskID: UUID) {
self.taskID = taskID.uuidString
}
// Sem diálogo para widgets
func perform() async throws -> some IntentResult {
guard let uuid = UUID(uuidString: taskID) else {
return .result()
}
TaskService.shared.toggleCompletion(taskId: uuid)
// Recarga imediata do widget
WidgetCenter.shared.reloadTimelines(ofKind: "TaskWidget")
return .result()
}
}
// View do widget com botão interativo
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)
}
}Os widgets usam a sintaxe Button(intent:) para conectar a interação diretamente ao App Intent, sem abrir o aplicativo.
Configuração do Action Button
O Action Button do iPhone 15 Pro e posteriores pode disparar App Shortcuts diretamente.
import AppIntents
// Intent otimizado para Action Button
struct QuickCaptureIntent: AppIntent {
static var title: LocalizedStringResource = "Captura rápida"
static var description = IntentDescription(
"Cria rapidamente uma tarefa com um título."
)
// Abre o app para entrada
static var openAppWhenRun: Bool = true
func perform() async throws -> some IntentResult & OpensIntent {
// Notificação para abrir a tela de captura rápida
NotificationCenter.default.post(
name: .quickCaptureTriggered,
object: nil
)
return .result(opensIntent: ShowQuickCaptureViewIntent())
}
}
// Declarar em AppShortcutsProvider para Action Button
extension TaskAppShortcutsProvider {
@AppShortcutsBuilder
static var appShortcuts: [AppShortcut] {
// ... outros atalhos
AppShortcut(
intent: QuickCaptureIntent(),
phrases: [
"Captura rápida \(.applicationName)",
"Nota rápida \(.applicationName)"
],
shortTitle: "Capturar",
systemImageName: "bolt.circle"
)
}
}Os usuários podem configurar o Action Button para disparar esse atalho via Ajustes > Botão de Ação.
iOS 18 introduz os macros @DeferredProperty e @ComputedProperty para reduzir o boilerplate. App Intents também podem residir em Swift Packages para reuso multiplataforma.
Intents em Swift Packages
Os App Intents podem ser definidos em Swift Packages para compartilhamento entre iOS, macOS e 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 compartilhado multiplataforma
public struct SharedCreateTaskIntent: AppIntent {
public static var title: LocalizedStringResource = "Criar uma tarefa"
@Parameter(title: "Título")
public var taskTitle: String
public init() {}
public func perform() async throws -> some IntentResult {
// Lógica compartilhada
await TaskRepository.shared.create(title: taskTitle)
return .result()
}
}
// Extensão específica de plataforma no app
#if os(iOS)
extension SharedCreateTaskIntent {
// Comportamento específico iOS
static var openAppWhenRun: Bool = false
}
#endifEssa arquitetura mantém um único código para os intents, adaptando o comportamento por plataforma.
Testar os App Intents
Os App Intents podem ser testados de forma unitária como qualquer código Swift.
import XCTest
import AppIntents
@testable import TaskApp
final class TaskIntentTests: XCTestCase {
override func setUp() {
super.setUp()
// Resetar serviço para testes isolados
TaskService.shared.reset()
}
func testCreateTaskIntent() async throws {
// Given
var intent = CreateTaskIntent()
intent.taskTitle = "Tarefa de teste"
intent.priority = .high
// When
let result = try await intent.perform()
// Then
let tasks = TaskService.shared.fetchTasks()
XCTAssertEqual(tasks.count, 1)
XCTAssertEqual(tasks.first?.title, "Tarefa de teste")
XCTAssertEqual(tasks.first?.priority, .high)
}
func testCompleteTaskIntent() async throws {
// Given
let task = TaskService.shared.createTask(
title: "Tarefa para concluir",
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: "Tarefa 1", priority: .low)
let task2 = TaskService.shared.createTask(title: "Tarefa 2", priority: .high)
let query = TaskEntityQuery()
// When
let entities = try await query.entities(for: [task1.id, task2.id])
// Then
XCTAssertEqual(entities.count, 2)
}
}Os testes verificam o comportamento dos intents independentemente da interface do sistema.
Boas práticas e otimizações
Vários padrões garantem App Intents performáticos e uma experiência ótima para o usuário.
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. Tratamento adequado de erros
enum TaskIntentError: Error, CustomLocalizedStringResourceConvertible {
case taskNotFound
case invalidInput
case serviceUnavailable
var localizedStringResource: LocalizedStringResource {
switch self {
case .taskNotFound:
return "Tarefa não encontrada."
case .invalidInput:
return "Dados inválidos."
case .serviceUnavailable:
return "Serviço temporariamente indisponível."
}
}
}
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: "Tarefa encontrada: \(task.title)")
}
}
// 3. Otimização de EntityQuery
struct OptimizedTaskQuery: EntityStringQuery {
// Busca textual otimizada
func entities(matching string: String) async throws -> [TaskEntity] {
// Busca no serviço com limite
TaskService.shared.search(query: string, limit: 10)
.map { TaskEntity(task: $0) }
}
// Sugestões limitadas para performance
func suggestedEntities() async throws -> [TaskEntity] {
TaskService.shared.fetchRecentTasks(limit: 5)
.map { TaskEntity(task: $0) }
}
}
// 4. Focus Filter para os modos de Foco
struct TaskFocusFilter: SetFocusFilterIntent {
static var title: LocalizedStringResource = "Filtrar tarefas"
@Parameter(title: "Mostrar apenas prioridade alta")
var showHighPriorityOnly: Bool
func perform() async throws -> some IntentResult {
TaskService.shared.setFocusFilter(highPriorityOnly: showHighPriorityOnly)
return .result()
}
}Esses padrões garantem uma integração fluida com o sistema preservando uma performance ótima.
Conclusão
App Intents e Siri Shortcuts transformam a forma como os usuários interagem com aplicações iOS. Em 2026, com Apple Intelligence, expor intents deixou de ser opcional e tornou-se essencial para entregar uma experiência moderna e intuitiva.
Checklist App Intents iOS 18+
- ✅ Criar AppIntents para as ações principais do app
- ✅ Definir AppEntities para os dados manipuláveis
- ✅ Usar AppEnum para os tipos enumerados
- ✅ Implementar AppShortcutsProvider com frases de voz
- ✅ Respeitar o limite máximo de 10 App Shortcuts
- ✅ Incluir
\(.applicationName)em todas as frases - ✅ Configurar ParameterSummary para feedback Siri
- ✅ Integrar domínios Apple Intelligence quando aplicável
- ✅ Testar os intents com testes unitários
- ✅ Localizar títulos e descrições
Comece a praticar!
Teste seus conhecimentos com nossos simuladores de entrevista e testes tecnicos.
Tags
Compartilhar
Artigos relacionados

WidgetKit iOS 17+: Widgets Interativos com App Intents
Guia completo para criar widgets iOS interativos com WidgetKit e App Intents. Botões, toggles, animações e melhores práticas para iOS 17+ em 2026.

Combine vs async/await em Swift: Padrões de Migração Progressiva
Guia completo para migrar de Combine para async/await em Swift: estratégias progressivas, padrões de ponte e coexistência de paradigmas em bases de código iOS.

Perguntas de entrevista sobre acessibilidade iOS em 2026: VoiceOver e Dynamic Type
Prepare-se para entrevistas iOS com perguntas-chave de acessibilidade: VoiceOver, Dynamic Type, traits semânticos e auditorias.