App Intents e Siri Shortcuts: automazione iOS avanzata 2026

Guida completa ad App Intents e Siri Shortcuts per iOS 18+. Creare azioni personalizzate per Siri, integrare Apple Intelligence e automatizzare l'app Swift nel 2026.

App Intents e Siri Shortcuts per automazione iOS avanzata con Swift e Apple Intelligence

Nel 2026, con Apple Intelligence e il framework App Intents, le applicazioni iOS entrano in un'era in cui l'intento dell'utente conta più dell'interfaccia grafica. Le app che non espongono intents diventano invisibili in un sistema operativo orientato all'IA. App Intents costituisce la fondazione che permette a Siri, Spotlight, ai widget e all'Action Button di interagire con le funzionalità dell'app.

Cosa copre questo articolo

Questo articolo presenta la creazione completa di App Intents e Siri Shortcuts per iOS 18+, dai concetti fondamentali fino all'integrazione con Apple Intelligence e gli App Intent Domains.

Comprendere il framework App Intents

Il framework App Intents, introdotto con iOS 16, modernizza la costruzione degli intents in Swift sostituendo il vecchio framework SiriKit Intents. Questa architettura dichiarativa consente di creare azioni rilevabili dal sistema: Spotlight, app Comandi rapidi, Siri e Action Button.

TaskIntent.swiftswift
import AppIntents

// Un AppIntent rappresenta un'azione che l'utente può eseguire
struct CreateTaskIntent: AppIntent {
    // Titolo mostrato in Comandi rapidi e Siri
    static var title: LocalizedStringResource = "Crea un'attività"

    // Descrizione per accessibilità e suggerimenti
    static var description = IntentDescription(
        "Crea una nuova attività nell'applicazione."
    )

    // Parametro con validazione automatica
    @Parameter(title: "Titolo dell'attività")
    var taskTitle: String

    // Parametro opzionale con valore di default
    @Parameter(title: "Priorità", default: .medium)
    var priority: TaskPriority

    // Esecuzione dell'azione
    func perform() async throws -> some IntentResult & ReturnsValue<TaskEntity> {
        // Crea l'attività tramite il service
        let task = TaskService.shared.createTask(
            title: taskTitle,
            priority: priority
        )

        // Restituisce l'entità creata per il concatenamento
        return .result(value: TaskEntity(task: task))
    }
}

L'intent dichiara i suoi parametri tramite il property wrapper @Parameter, permettendo a Siri di richiedere i valori mancanti. Il metodo perform() esegue la logica di business e restituisce un risultato tipizzato.

Definire App Entities per i dati

Le App Entities rappresentano i "sostantivi" dell'applicazione: gli oggetti su cui operano gli intents. Permettono a Siri di comprendere e manipolare i dati dell'app.

TaskEntity.swiftswift
import AppIntents

// Modello dati interno
struct Task: Identifiable, Codable {
    let id: UUID
    var title: String
    var priority: TaskPriority
    var isCompleted: Bool
    var dueDate: Date?
}

// Entità esposta al sistema
struct TaskEntity: AppEntity {
    // Identificatore univoco richiesto
    var id: UUID

    // Proprietà visualizzabili
    var title: String
    var priority: TaskPriority
    var isCompleted: Bool

    // Configurazione di visualizzazione nel sistema
    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Attività"

    // Rappresentazione visiva dell'istanza
    var displayRepresentation: DisplayRepresentation {
        DisplayRepresentation(
            title: "\(title)",
            subtitle: "\(priority.rawValue)",
            image: .init(systemName: isCompleted ? "checkmark.circle.fill" : "circle")
        )
    }

    // Query di default per la ricerca delle entità
    static var defaultQuery = TaskEntityQuery()

    // Initializer dal modello interno
    init(task: Task) {
        self.id = task.id
        self.title = task.title
        self.priority = task.priority
        self.isCompleted = task.isCompleted
    }
}

// Query per ricercare e filtrare le entità
struct TaskEntityQuery: EntityQuery {
    // Ricerca per identificatori
    func entities(for identifiers: [UUID]) async throws -> [TaskEntity] {
        TaskService.shared.fetchTasks()
            .filter { identifiers.contains($0.id) }
            .map { TaskEntity(task: $0) }
    }

    // Suggerimenti mostrati nell'interfaccia
    func suggestedEntities() async throws -> [TaskEntity] {
        TaskService.shared.fetchTasks()
            .filter { !$0.isCompleted }
            .prefix(5)
            .map { TaskEntity(task: $0) }
    }
}

L'EntityQuery definisce come il sistema cerca e suggerisce le entità. I metodi entities(for:) e suggestedEntities() alimentano le interfacce di Siri e dei Comandi rapidi.

App Enums vs App Entities

Usare AppEnum per tipi con un insieme fisso di valori (priorità, stato), e AppEntity per tipi dinamici creati dall'utente (attività, note, contatti).

Creare App Enums per valori fissi

Gli App Enums espongono tipi enumerati al sistema, permettendo a Siri di offrire scelte contestuali.

TaskPriority.swiftswift
import AppIntents

// Enum esposto al sistema
enum TaskPriority: String, AppEnum, Codable {
    case low
    case medium
    case high

    // Nome del tipo visualizzato
    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Priorità"

    // Rappresentazione di ogni caso
    static var caseDisplayRepresentations: [TaskPriority: DisplayRepresentation] = [
        .low: DisplayRepresentation(
            title: "Bassa",
            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 per lo stato dell'attività
enum TaskStatus: String, AppEnum, Codable {
    case pending
    case inProgress
    case completed

    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Stato"

    static var caseDisplayRepresentations: [TaskStatus: DisplayRepresentation] = [
        .pending: "In attesa",
        .inProgress: "In corso",
        .completed: "Completata"
    ]
}

Le rappresentazioni visive (icone SF Symbols) arricchiscono la visualizzazione nei Comandi rapidi e nei suggerimenti di Siri.

Implementare AppShortcutsProvider

L'AppShortcutsProvider espone gli App Shortcuts al sistema, rendendoli immediatamente disponibili senza configurazione utente. Questi shortcut compaiono in Spotlight, Siri e Action Button.

ShortcutsProvider.swiftswift
import AppIntents

// Provider che dichiara tutti gli shortcut dell'app
struct TaskAppShortcutsProvider: AppShortcutsProvider {
    // Massimo 10 shortcut per applicazione
    @AppShortcutsBuilder
    static var appShortcuts: [AppShortcut] {
        // Shortcut per creare un'attività
        AppShortcut(
            intent: CreateTaskIntent(),
            phrases: [
                // Il placeholder .applicationName è OBBLIGATORIO
                "Crea un'attività con \(.applicationName)",
                "Nuova attività in \(.applicationName)",
                "Aggiungi un'attività a \(.applicationName)"
            ],
            shortTitle: "Crea attività",
            systemImageName: "plus.circle"
        )

        // Shortcut per elencare le attività
        AppShortcut(
            intent: ListTasksIntent(),
            phrases: [
                "Mostra le mie attività in \(.applicationName)",
                "Visualizza le attività di \(.applicationName)",
                "Quali sono le mie attività \(.applicationName)"
            ],
            shortTitle: "Le mie attività",
            systemImageName: "list.bullet"
        )

        // Shortcut con parametro dinamico
        AppShortcut(
            intent: CompleteTaskIntent(),
            phrases: [
                "Completa \(\.$taskName) in \(.applicationName)",
                "Segna \(\.$taskName) come fatta con \(.applicationName)"
            ],
            shortTitle: "Completa attività",
            systemImageName: "checkmark.circle"
        )
    }
}

Le frasi vocali devono includere il placeholder \(.applicationName) affinché Siri identifichi l'app di destinazione. I parametri dinamici come \(\.$taskName) consentono comandi contestuali.

Limite di 10 Shortcuts

Un'applicazione può dichiarare al massimo 10 App Shortcuts. Conviene dare priorità alle azioni più frequenti e utili per gli utenti.

Intents con parametri complessi

Gli intents possono accettare parametri complessi, incluse entità e opzioni di configurazione.

CompleteTaskIntent.swiftswift
import AppIntents

struct CompleteTaskIntent: AppIntent {
    static var title: LocalizedStringResource = "Completa un'attività"

    static var description = IntentDescription(
        "Segna un'attività come completata."
    )

    // Parametro entità con ricerca automatica
    @Parameter(title: "Attività")
    var task: TaskEntity

    // Parametro opzionale con data
    @Parameter(title: "Data di completamento")
    var completionDate: Date?

    // Configurazione del dialogo Siri
    static var parameterSummary: some ParameterSummary {
        Summary("Completa \(\.$task)") {
            \.$completionDate
        }
    }

    func perform() async throws -> some IntentResult & ProvidesDialog {
        // Aggiorna l'attività
        TaskService.shared.completeTask(
            id: task.id,
            completionDate: completionDate ?? Date()
        )

        // Feedback vocale per Siri
        return .result(
            dialog: "L'attività \(task.title) è stata segnata come completata."
        )
    }
}

// Intent che restituisce una lista di entità
struct ListTasksIntent: AppIntent {
    static var title: LocalizedStringResource = "Elenca attività"

    // Filtro opzionale
    @Parameter(title: "Stato", default: nil)
    var statusFilter: TaskStatus?

    @Parameter(title: "Priorità", default: nil)
    var priorityFilter: TaskPriority?

    func perform() async throws -> some IntentResult & ReturnsValue<[TaskEntity]> {
        var tasks = TaskService.shared.fetchTasks()

        // Applica i filtri
        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)
    }
}

Il ParameterSummary definisce come Siri presenta l'intent durante l'esecuzione vocale, fornendo un feedback naturale.

Pronto a superare i tuoi colloqui su iOS?

Pratica con i nostri simulatori interattivi, flashcards e test tecnici.

Integrazione con Apple Intelligence

iOS 18 introduce gli App Intent Domains, raccolte di API progettate per funzionalità specifiche. Questi domini consentono ad Apple Intelligence di comprendere ed eseguire azioni con maggiore precisione.

BookmarkDomainIntents.swiftswift
import AppIntents

// Conformità al dominio Bookmarks per integrazione Apple Intelligence
struct SaveBookmarkIntent: AppIntent {
    static var title: LocalizedStringResource = "Salva un segnalibro"

    // Parametro URL validato automaticamente
    @Parameter(title: "URL")
    var url: URL

    @Parameter(title: "Titolo", default: nil)
    var title: String?

    @Parameter(title: "Cartella", default: nil)
    var folder: BookmarkFolderEntity?

    // Apri l'app se necessario
    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: "Segnalibro salvato: \(bookmark.title)"
        )
    }
}

// Intent con consapevolezza del contenuto a schermo (iOS 18.4+)
struct AnalyzeScreenContentIntent: AppIntent {
    static var title: LocalizedStringResource = "Analizza contenuto"

    // Accesso al contesto della schermata via Apple Intelligence
    @Parameter(title: "Contesto")
    var screenContext: String?

    func perform() async throws -> some IntentResult & ProvidesDialog {
        guard let context = screenContext else {
            return .result(dialog: "Nessun contenuto da analizzare.")
        }

        // Elabora il contenuto estratto da Apple Intelligence
        let analysis = ContentAnalyzer.analyze(context)

        return .result(dialog: analysis.summary)
    }
}

I domini predefiniti (Books, Camera, Spreadsheets) permettono a Siri di rispondere alle richieste con precisione grazie a modelli addestrati su questi compiti specifici.

Azioni Siri con conferma utente

Per azioni sensibili, il sistema può richiedere conferma prima dell'esecuzione.

DeleteTaskIntent.swiftswift
import AppIntents

struct DeleteTaskIntent: AppIntent {
    static var title: LocalizedStringResource = "Elimina un'attività"

    @Parameter(title: "Attività")
    var task: TaskEntity

    // Richiede conferma utente
    static var isDiscoverable: Bool = true

    func perform() async throws -> some IntentResult & ProvidesDialog {
        // Richiedi conferma tramite dialogo
        try await requestConfirmation(
            result: .result(
                dialog: "Confermare l'eliminazione di \(task.title)?"
            )
        )

        // Elimina dopo conferma
        TaskService.shared.deleteTask(id: task.id)

        return .result(
            dialog: "L'attività \(task.title) è stata eliminata."
        )
    }
}

// Intent con più step di dialogo
struct ScheduleTaskIntent: AppIntent {
    static var title: LocalizedStringResource = "Pianifica un'attività"

    @Parameter(title: "Attività")
    var task: TaskEntity

    @Parameter(title: "Data")
    var scheduledDate: Date

    @Parameter(title: "Promemoria", default: true)
    var setReminder: Bool

    static var parameterSummary: some ParameterSummary {
        When(\.$setReminder, .equalTo, true) {
            Summary("Pianifica \(\.$task) per \(\.$scheduledDate) con promemoria")
        } otherwise: {
            Summary("Pianifica \(\.$task) per \(\.$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: "Attività pianificata per \(formattedDate)."
        )
    }
}

Il metodo requestConfirmation mette in pausa l'esecuzione fino alla conferma dell'utente, proteggendo da azioni accidentali.

Intents nei widget interattivi

Gli App Intents si integrano naturalmente con WidgetKit per creare widget interattivi su iOS 17+.

TaskWidgetIntents.swiftswift
import AppIntents
import WidgetKit

// Intent ottimizzato per widget (esecuzione rapida)
struct ToggleTaskFromWidgetIntent: AppIntent {
    static var title: LocalizedStringResource = "Alterna attività"

    @Parameter(title: "ID attività")
    var taskID: String

    init() {}

    init(taskID: UUID) {
        self.taskID = taskID.uuidString
    }

    // Nessun dialogo per i widget
    func perform() async throws -> some IntentResult {
        guard let uuid = UUID(uuidString: taskID) else {
            return .result()
        }

        TaskService.shared.toggleCompletion(taskId: uuid)

        // Aggiornamento immediato del widget
        WidgetCenter.shared.reloadTimelines(ofKind: "TaskWidget")

        return .result()
    }
}

// View del widget con bottone interattivo
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)
    }
}

I widget usano la sintassi Button(intent:) per collegare l'interazione direttamente all'App Intent, senza aprire l'applicazione.

Configurazione dell'Action Button

L'Action Button su iPhone 15 Pro e successivi può attivare App Shortcuts direttamente.

ActionButtonIntent.swiftswift
import AppIntents

// Intent ottimizzato per Action Button
struct QuickCaptureIntent: AppIntent {
    static var title: LocalizedStringResource = "Cattura rapida"

    static var description = IntentDescription(
        "Crea rapidamente un'attività con un titolo."
    )

    // Apre l'app per l'input
    static var openAppWhenRun: Bool = true

    func perform() async throws -> some IntentResult & OpensIntent {
        // Notifica per aprire la schermata di cattura rapida
        NotificationCenter.default.post(
            name: .quickCaptureTriggered,
            object: nil
        )

        return .result(opensIntent: ShowQuickCaptureViewIntent())
    }
}

// Dichiarare in AppShortcutsProvider per Action Button
extension TaskAppShortcutsProvider {
    @AppShortcutsBuilder
    static var appShortcuts: [AppShortcut] {
        // ... altri shortcut

        AppShortcut(
            intent: QuickCaptureIntent(),
            phrases: [
                "Cattura rapida \(.applicationName)",
                "Nota rapida \(.applicationName)"
            ],
            shortTitle: "Cattura",
            systemImageName: "bolt.circle"
        )
    }
}

Gli utenti possono configurare l'Action Button per attivare questo shortcut tramite Impostazioni > Tasto Azione.

Macro iOS 18+

iOS 18 introduce i macro @DeferredProperty e @ComputedProperty per ridurre il boilerplate. Gli App Intents possono inoltre risiedere in Swift Packages per il riutilizzo cross-platform.

Intents in Swift Packages

Gli App Intents possono essere definiti in Swift Packages per la condivisione tra 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 condiviso multipiattaforma
public struct SharedCreateTaskIntent: AppIntent {
    public static var title: LocalizedStringResource = "Crea un'attività"

    @Parameter(title: "Titolo")
    public var taskTitle: String

    public init() {}

    public func perform() async throws -> some IntentResult {
        // Logica condivisa
        await TaskRepository.shared.create(title: taskTitle)
        return .result()
    }
}

// Estensione specifica per piattaforma nell'app
#if os(iOS)
extension SharedCreateTaskIntent {
    // Comportamento specifico iOS
    static var openAppWhenRun: Bool = false
}
#endif

Questa architettura mantiene un'unica base di codice per gli intents adattando il comportamento per piattaforma.

Testare gli App Intents

Gli App Intents possono essere testati a livello unitario come qualsiasi codice Swift.

TaskIntentTests.swiftswift
import XCTest
import AppIntents
@testable import TaskApp

final class TaskIntentTests: XCTestCase {

    override func setUp() {
        super.setUp()
        // Reset del service per test isolati
        TaskService.shared.reset()
    }

    func testCreateTaskIntent() async throws {
        // Given
        var intent = CreateTaskIntent()
        intent.taskTitle = "Attività di test"
        intent.priority = .high

        // When
        let result = try await intent.perform()

        // Then
        let tasks = TaskService.shared.fetchTasks()
        XCTAssertEqual(tasks.count, 1)
        XCTAssertEqual(tasks.first?.title, "Attività di test")
        XCTAssertEqual(tasks.first?.priority, .high)
    }

    func testCompleteTaskIntent() async throws {
        // Given
        let task = TaskService.shared.createTask(
            title: "Attività da completare",
            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: "Attività 1", priority: .low)
        let task2 = TaskService.shared.createTask(title: "Attività 2", priority: .high)

        let query = TaskEntityQuery()

        // When
        let entities = try await query.entities(for: [task1.id, task2.id])

        // Then
        XCTAssertEqual(entities.count, 2)
    }
}

I test verificano il comportamento degli intents indipendentemente dall'interfaccia di sistema.

Best practice e ottimizzazioni

Diversi pattern garantiscono App Intents performanti e un'esperienza utente ottimale.

BestPractices.swiftswift
import AppIntents

// 1. Frasi localizzate
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. Gestione corretta degli errori
enum TaskIntentError: Error, CustomLocalizedStringResourceConvertible {
    case taskNotFound
    case invalidInput
    case serviceUnavailable

    var localizedStringResource: LocalizedStringResource {
        switch self {
        case .taskNotFound:
            return "Attività non trovata."
        case .invalidInput:
            return "Dati non validi."
        case .serviceUnavailable:
            return "Servizio temporaneamente non disponibile."
        }
    }
}

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: "Attività trovata: \(task.title)")
    }
}

// 3. Ottimizzazione di EntityQuery
struct OptimizedTaskQuery: EntityStringQuery {
    // Ricerca testuale ottimizzata
    func entities(matching string: String) async throws -> [TaskEntity] {
        // Ricerca sul service con limite
        TaskService.shared.search(query: string, limit: 10)
            .map { TaskEntity(task: $0) }
    }

    // Suggerimenti limitati per le performance
    func suggestedEntities() async throws -> [TaskEntity] {
        TaskService.shared.fetchRecentTasks(limit: 5)
            .map { TaskEntity(task: $0) }
    }
}

// 4. Focus Filter per le modalità Full Immersion
struct TaskFocusFilter: SetFocusFilterIntent {
    static var title: LocalizedStringResource = "Filtra attività"

    @Parameter(title: "Mostra solo priorità alta")
    var showHighPriorityOnly: Bool

    func perform() async throws -> some IntentResult {
        TaskService.shared.setFocusFilter(highPriorityOnly: showHighPriorityOnly)
        return .result()
    }
}

Questi pattern garantiscono un'integrazione fluida con il sistema preservando performance ottimali.

Conclusione

App Intents e Siri Shortcuts trasformano il modo in cui gli utenti interagiscono con le applicazioni iOS. Nel 2026, con Apple Intelligence, esporre intents non è più opzionale ma essenziale per offrire un'esperienza moderna e intuitiva.

Checklist App Intents iOS 18+

  • ✅ Creare AppIntents per le azioni principali dell'app
  • ✅ Definire AppEntities per i dati manipolabili
  • ✅ Usare AppEnum per i tipi enumerati
  • ✅ Implementare AppShortcutsProvider con frasi vocali
  • ✅ Rispettare il limite massimo di 10 App Shortcuts
  • ✅ Includere \(.applicationName) in tutte le frasi
  • ✅ Configurare ParameterSummary per il feedback Siri
  • ✅ Integrare i domini Apple Intelligence se applicabile
  • ✅ Testare gli intents con test unitari
  • ✅ Localizzare titoli e descrizioni

Inizia a praticare!

Metti alla prova le tue conoscenze con i nostri simulatori di colloquio e test tecnici.

Tag

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

Condividi

Articoli correlati