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.

App Intents e Siri Shortcuts para automação iOS avançada com Swift e Apple Intelligence

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.

O que este artigo cobre

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.

TaskIntent.swiftswift
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.

TaskEntity.swiftswift
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.

App Enums vs App Entities

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.

TaskPriority.swiftswift
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.

ShortcutsProvider.swiftswift
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.

Limite de 10 Shortcuts

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.

CompleteTaskIntent.swiftswift
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.

BookmarkDomainIntents.swiftswift
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.

DeleteTaskIntent.swiftswift
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+.

TaskWidgetIntents.swiftswift
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.

ActionButtonIntent.swiftswift
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.

Macros iOS 18+

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.

Package.swiftswift
// 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
}
#endif

Essa 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.

TaskIntentTests.swiftswift
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.

BestPractices.swiftswift
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

#app-intents
#siri-shortcuts
#ios
#swift
#apple-intelligence

Compartilhar

Artigos relacionados