App Intents in iOS 26: Guida Completa a Siri, Spotlight e Apple Intelligence con Swift

Guida pratica e completa al framework App Intents in iOS 26: AppEntity con SwiftData, EntityQuery con @Dependency, App Shortcut, integrazione Spotlight semantica e Apple Intelligence in Swift.

App Intents iOS 26: Guida Siri e AI 2026

Introduzione: perché App Intents è il framework più importante del 2026

Sta succedendo qualcosa di silenzioso ma profondo nello sviluppo iOS, e onestamente molti sviluppatori lo stanno ancora sottovalutando. Nel 2026, con iOS 26, l'icona della tua app non è più l'unico modo in cui gli utenti la usano. Siri, Spotlight, l'Action Button, gli Shortcut, i Widget, persino Apple Intelligence: tutti questi punti d'ingresso al sistema parlano la stessa lingua, e quella lingua è App Intents.

Te lo dico in modo brutale: se la tua app non espone le sue funzioni come App Intent, in un sistema operativo sempre più orientato all'IA semplicemente non esiste. Spotlight non la troverà semanticamente, Apple Intelligence non potrà invocarla, e l'utente che chiede a Siri "aggiungi pomodori alla lista della spesa" finirà per usare l'app del concorrente che ha fatto i compiti.

Questa guida è il riferimento pratico che mi sarebbe servito quando ho integrato App Intents in produzione (e ho perso una sera intera a capire perché Siri non riconosceva le mie frasi — spoiler: mancava un segnaposto, ne parliamo dopo). Copre la sintassi moderna introdotta a WWDC25, l'integrazione con SwiftData tramite @Dependency, l'iniezione semantica in Spotlight, le App Shortcut con frasi multiple, e tutto ciò che ho imparato a non fare. Andremo più in profondità della documentazione ufficiale, e con esempi più realistici di quelli che si trovano nei tutorial in giro.

Cos'è il framework App Intents

App Intents è un framework dichiarativo introdotto con iOS 16 che ha sostituito la vecchia SiriKit Intents framework (quella basata sui file .intentdefinition, che chiunque l'abbia usata ricorda con un brivido). Consente di descrivere in puro Swift le azioni che la tua app può eseguire, i dati che gestisce e le loro relazioni, in modo che il sistema operativo possa indicizzare, suggerire ed eseguire queste azioni in qualsiasi punto delle sue superfici.

I tre concetti cardine sono:

  • App Intent: un'azione (un verbo). "Crea un promemoria", "Avvia allenamento", "Aggiungi articolo al carrello".
  • App Entity: un dato (un sostantivo) con identificatore e rappresentazione visuale. Un promemoria, un allenamento, un prodotto.
  • App Shortcut: un intent promosso a livello di sistema, attivabile con frasi vocali, dal pulsante Action, da Spotlight o dall'app Comandi rapidi senza alcuna configurazione utente.

Su iOS 26 questa architettura si è arricchita di tre novità sostanziali che cambiano il modo in cui scrivi il codice di integrazione: @ComputedProperty per proprietà derivate dinamicamente, l'iniezione di dipendenze tramite @Dependency direttamente nelle EntityQuery, e i Visual Intents che agganciano App Intents a Visual Intelligence per l'analisi di immagini sulla fotocamera.

Il primo App Intent: anatomia di un'azione

Cominciamo dal minimo sindacale. Un App Intent è un struct che conforma il protocollo AppIntent, dichiara un titolo e implementa un metodo perform() asincrono.

import AppIntents

struct CreateNoteIntent: AppIntent {
    static let title: LocalizedStringResource = "Crea una nota"
    static let description = IntentDescription(
        "Crea una nuova nota con il testo specificato.",
        categoryName: "Produttività"
    )

    @Parameter(title: "Testo della nota", inputOptions: .init(multiline: true))
    var content: String

    @Parameter(title: "Importante", default: false)
    var isImportant: Bool

    static var parameterSummary: some ParameterSummary {
        Summary("Crea una nota con \(\.$content)") {
            \.$isImportant
        }
    }

    func perform() async throws -> some IntentResult & ProvidesDialog {
        let note = NoteStore.shared.create(content: content, important: isImportant)
        return .result(dialog: "Nota creata: \(note.title)")
    }
}

Tre cose da notare qui. Primo, i @Parameter sono i campi che l'utente compila quando invoca l'intent da Comandi rapidi, e che Siri estrae dalla frase pronunciata. Secondo, parameterSummary è la stringa visualizzata nell'app Comandi rapidi: usa l'operatore \.$content per inserire un parametro inline, e racchiudi i parametri "secondari" tra parentesi graffe perché compaiano nella sezione "Mostra altro". Terzo (e questo è il punto che confonde molti la prima volta), il valore restituito da perform() non è un semplice Void: è un tipo composto che combina il risultato vero e proprio (IntentResult) con eventuali estensioni come ProvidesDialog, ReturnsValue o OpensIntent.

Tipi di risultato che dovresti conoscere

Il valore restituito dal perform() determina come Siri e Comandi rapidi presentano il risultato all'utente:

  • .result() — esecuzione silenziosa senza output.
  • .result(dialog:) — Siri pronuncia il dialogo dopo l'esecuzione.
  • .result(value:) — restituisce un valore (un'AppEntity, un numero, una stringa) usabile come input in un altro passo della shortcut.
  • .result(opensIntent:) — incatena un altro intent al termine.
  • .result(view:) — su iOS 26 puoi restituire una SwiftUI snippet view visualizzata in linea nell'overlay di Siri (è la novità che ho aspettato di più, francamente).

App Entity: descrivere i dati al sistema

Le App Entity sono il modo in cui il sistema "capisce" gli oggetti della tua app. Un'App Entity ha un id stabile, una DisplayRepresentation che il sistema mostra all'utente, e un'EntityQuery che insegna al sistema come recuperarla.

Supponiamo di costruire un'app per una lista di luoghi salvati. Il modello SwiftData è semplice:

import SwiftData

@Model
final class Landmark {
    @Attribute(.unique) var id: UUID
    var name: String
    var subtitle: String
    var latitude: Double
    var longitude: Double
    var visited: Bool

    init(id: UUID = UUID(), name: String, subtitle: String,
         latitude: Double, longitude: Double, visited: Bool = false) {
        self.id = id
        self.name = name
        self.subtitle = subtitle
        self.latitude = latitude
        self.longitude = longitude
        self.visited = visited
    }
}

L'App Entity corrispondente avvolge il modello senza duplicarne i dati. La novità più importante di iOS 26 è @ComputedProperty, che evita di copiare manualmente le proprietà nell'init: il sistema le legge sempre dal modello sorgente, sempre fresche. Una piccola comodità che, moltiplicata per dieci entità, fa la differenza.

import AppIntents

struct LandmarkEntity: AppEntity {
    static let typeDisplayRepresentation = TypeDisplayRepresentation(
        name: "Luogo"
    )
    static let defaultQuery = LandmarkEntityQuery()

    let landmark: Landmark

    var id: UUID { landmark.id }

    @ComputedProperty
    var name: String { landmark.name }

    @ComputedProperty
    var subtitle: String { landmark.subtitle }

    @ComputedProperty
    var visited: Bool { landmark.visited }

    var displayRepresentation: DisplayRepresentation {
        DisplayRepresentation(
            title: "\(name)",
            subtitle: "\(subtitle)",
            image: visited ? .init(systemName: "checkmark.circle.fill")
                           : .init(systemName: "mappin.circle")
        )
    }
}

Il pattern è chiaro: il Landmark SwiftData è la fonte della verità, l'AppEntity è la sua proiezione "comprensibile dal sistema". Mai persistere stato dentro l'AppEntity; mai esporre il modello SwiftData direttamente al framework App Intents. Su questo non transigo — ci ho sbattuto la testa abbastanza volte da poter dire che è una regola, non un suggerimento.

EntityQuery con @Dependency: il pattern WWDC25

Qui sta una delle confusioni più frequenti. Le EntityQuery vengono istanziate dal sistema, non da te. Il sistema può crearle in un processo extension separato dalla tua app principale, magari quando l'app non è nemmeno in esecuzione (Siri sta ascoltando, l'utente preme il pulsante Action). Come fa la query a sapere dove pescare i dati?

Prima di iOS 26 dovevi usare singleton globali pigramente inizializzati o creare manualmente un ModelContainer per ogni invocazione. Ora il pattern ufficiale è AppDependencyManager + @Dependency, e — finalmente — sembra una soluzione progettata da chi scrive davvero codice.

1. Registra le dipendenze all'avvio

import SwiftUI
import SwiftData
import AppIntents

@main
struct LandmarksApp: App {
    let modelContainer: ModelContainer = {
        do {
            return try ModelContainer(for: Landmark.self)
        } catch {
            fatalError("Impossibile creare ModelContainer: \(error)")
        }
    }()

    init() {
        AppDependencyManager.shared.add(
            key: "ModelContainer"
        ) { @MainActor in
            modelContainer
        }
        LandmarkShortcuts.updateAppShortcutParameters()
    }

    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(modelContainer)
    }
}

La closure @MainActor e la key: esplicita non sono opzionali su Swift 6: sono richieste dal runtime di concorrenza per garantire l'isolamento corretto. Lo dico perché il primo errore di compilazione che vedrai sarà proprio questo.

2. Usa la dipendenza nella query

struct LandmarkEntityQuery: EntityQuery {
    @Dependency(key: "ModelContainer")
    private var modelContainer: ModelContainer

    func entities(for identifiers: [LandmarkEntity.ID]) async throws -> [LandmarkEntity] {
        let context = ModelContext(modelContainer)
        let descriptor = FetchDescriptor<Landmark>(
            predicate: #Predicate { identifiers.contains($0.id) }
        )
        return try context.fetch(descriptor).map(LandmarkEntity.init)
    }

    func suggestedEntities() async throws -> [LandmarkEntity] {
        let context = ModelContext(modelContainer)
        var descriptor = FetchDescriptor<Landmark>(
            sortBy: [SortDescriptor(\.name)]
        )
        descriptor.fetchLimit = 10
        return try context.fetch(descriptor).map(LandmarkEntity.init)
    }
}

Il metodo suggestedEntities() è cruciale: è ciò che l'app Comandi rapidi propone quando l'utente sta configurando una shortcut e deve scegliere un valore di parametro. Dieci elementi sono un buon limite predefinito (di più tendono solo a confondere).

3. Estendi con ricerca testuale

Per consentire a Siri di trovare un'entità per nome ("avvia il viaggio a Roma"), conforma EntityStringQuery:

extension LandmarkEntityQuery: EntityStringQuery {
    func entities(matching query: String) async throws -> [LandmarkEntity] {
        let context = ModelContext(modelContainer)
        let descriptor = FetchDescriptor<Landmark>(
            predicate: #Predicate { $0.name.localizedStandardContains(query) }
        )
        return try context.fetch(descriptor).map(LandmarkEntity.init)
    }
}

App Shortcut: rendere visibili le tue azioni

Definire un App Intent non basta. Per ottenere visibilità in Spotlight e Siri senza configurazione utente, devi creare un AppShortcutsProvider. Apple permette un solo provider per app, e in esso dichiari fino a 10 App Shortcut.

struct LandmarkShortcuts: AppShortcutsProvider {
    static var appShortcuts: [AppShortcut] {
        AppShortcut(
            intent: VisitLandmarkIntent(),
            phrases: [
                "Visita un luogo in \(.applicationName)",
                "Segna come visitato in \(.applicationName)",
                "Aggiorna luogo visitato in \(.applicationName)"
            ],
            shortTitle: "Visita un luogo",
            systemImageName: "mappin.and.ellipse"
        )

        AppShortcut(
            intent: CreateNoteIntent(),
            phrases: [
                "Nuova nota in \(.applicationName)",
                "Crea nota in \(.applicationName)",
                "Aggiungi promemoria in \(.applicationName)"
            ],
            shortTitle: "Nuova nota",
            systemImageName: "note.text.badge.plus"
        )
    }

    static var shortcutTileColor: ShortcutTileColor = .teal
}

Tre regole non negoziabili sulle frasi:

  1. Ogni frase deve contenere il segnaposto \(.applicationName). Senza, la registrazione fallisce silenziosamente. Sì, è quello l'errore di cui parlavo all'inizio.
  2. Includi sinonimi naturali. L'utente non dirà mai esattamente la frase canonica; coprire variazioni ("crea", "nuova", "aggiungi") aumenta drasticamente il riconoscimento.
  3. Esiste un limite duro di 1.000 frasi totali per app, e ogni opzione di un parametro enumerabile moltiplica le frasi. Se hai 5 frasi per una shortcut con 20 entità possibili, hai già consumato 100 frasi. Pianifica.

Integrazione Spotlight: ricerca semantica con IndexedEntity

Quando un utente cerca in Spotlight su iOS 26, il sistema interroga le tue App Entity tramite EntityStringQuery, ma può fare di più se aderisci al protocollo IndexedEntity. Questo abilita la ricerca semantica: Spotlight non confronta solo stringhe, ma usa embedding per capire che "posto in cui sono stato in vacanza al mare" mappa al tuo luogo "Costiera Amalfitana". È magia? No, ma fa abbastanza effetto la prima volta che funziona.

extension LandmarkEntity: IndexedEntity {
    var attributeSet: CSSearchableItemAttributeSet {
        let attributes = CSSearchableItemAttributeSet(contentType: .item)
        attributes.title = name
        attributes.contentDescription = subtitle
        attributes.keywords = [name, "luogo", "viaggio"]
        attributes.latitude = NSNumber(value: landmark.latitude)
        attributes.longitude = NSNumber(value: landmark.longitude)
        return attributes
    }
}

Per agganciare l'apertura diretta di una vista quando l'utente tocca il risultato in Spotlight, definisci un OpenIntent:

struct OpenLandmarkIntent: OpenIntent {
    static let title: LocalizedStringResource = "Apri luogo"

    @Parameter(title: "Luogo")
    var target: LandmarkEntity

    @MainActor
    func perform() async throws -> some IntentResult {
        NavigationModel.shared.navigate(to: target.id)
        return .result()
    }
}

Il fatto chiave: OpenIntent impone automaticamente al sistema di portare in primo piano la tua app prima dell'esecuzione del perform(), quindi puoi accedere al main actor in sicurezza.

Apple Intelligence e App Intents: la differenza nel 2026

Su iOS 26, Apple Intelligence (quando disponibile) può concatenare i tuoi App Intent in modo autonomo per soddisfare richieste complesse. Se l'utente chiede a Siri "ricordami di chiamare Marco quando arrivo a Milano", il sistema può combinare un CreateReminderIntent con un trigger geografico esposto da un'altra app. È qui che le cose si fanno serie.

Per essere "buoni cittadini" in questo ecosistema occorrono alcune accortezze:

  • Idempotenza: gli intent dovrebbero produrre lo stesso risultato se invocati due volte con gli stessi parametri. Apple Intelligence può effettuare retry, e un intent che duplica una nota a ogni retry diventa un disastro (te lo dico per esperienza).
  • Validazione esplicita dei parametri: usa requestValueDialog e requestConfirmation per chiedere chiarimenti invece di indovinare valori mancanti.
  • Localizzazione delle frasi: ogni stringa visibile usa LocalizedStringResource. Le frasi delle shortcut vivono nel file AppShortcuts.xcstrings generato automaticamente da Xcode 26.

Richiedere conferma e valori dinamicamente

struct DeleteLandmarkIntent: AppIntent {
    static let title: LocalizedStringResource = "Elimina luogo"
    static let isDiscoverable = true

    @Parameter(title: "Luogo")
    var target: LandmarkEntity

    @Dependency(key: "ModelContainer")
    private var modelContainer: ModelContainer

    func perform() async throws -> some IntentResult & ProvidesDialog {
        try await requestConfirmation(
            result: .result(dialog: "Confermi l'eliminazione di \(target.name)?")
        )

        let context = ModelContext(modelContainer)
        if let stored = try context.fetch(
            FetchDescriptor<Landmark>(predicate: #Predicate { $0.id == target.id })
        ).first {
            context.delete(stored)
            try context.save()
        }

        return .result(dialog: "Luogo eliminato.")
    }
}

Widget configurabili: App Intents incontrano WidgetKit

Le configurazioni dei widget non sono più basate sui vecchi file .intentdefinition (e meno male). Su iOS 26 si usa un WidgetConfigurationIntent che è un AppIntent specializzato:

struct LandmarkWidgetConfiguration: WidgetConfigurationIntent {
    static let title: LocalizedStringResource = "Configura widget"
    static let description = IntentDescription("Scegli quale luogo mostrare.")

    @Parameter(title: "Luogo")
    var landmark: LandmarkEntity?

    @Parameter(title: "Mostra distanza", default: true)
    var showDistance: Bool
}

L'utente che fa long-press sul widget vede automaticamente la stessa interfaccia di Comandi rapidi: il selettore del luogo è popolato dalla tua LandmarkEntityQuery.suggestedEntities(), lo switch è il booleano. Zero codice UI in più. Questa è una di quelle cose per cui ti chiedi come abbiamo vissuto senza, prima.

Test degli App Intent

Gli App Intent sono codice Swift normale: testabili come qualsiasi cosa. Su Xcode 26 con Swift Testing il pattern è particolarmente snello.

import Testing
@testable import Landmarks

@Suite("App Intents")
struct LandmarkIntentTests {
    @Test("Crea nota imposta il flag importante")
    func createNoteSetsImportantFlag() async throws {
        let intent = CreateNoteIntent()
        intent.content = "Test"
        intent.isImportant = true

        _ = try await intent.perform()

        let stored = NoteStore.shared.allNotes()
        #expect(stored.last?.important == true)
    }
}

Per gli intent che usano @Dependency, registra un container in-memory nel init() della suite di test, così le query funzionano senza avviare l'app.

Errori che vedo continuamente nel code review

Dopo aver supervisionato decine di integrazioni App Intents, questi sono gli errori che si ripresentano sempre — e sì, ne ho commessi molti anch'io:

  1. Modello SwiftData esposto direttamente come AppEntity. Tentazione comune; il framework può scegliere di serializzare l'entità su disco o tra processi. Mantieni l'AppEntity come proiezione separata.
  2. Frasi senza il segnaposto \(.applicationName). Niente registrazione, niente errore visibile, ore perse a chiedersi perché Siri non risponde. (Sì, di nuovo.)
  3. Dimenticare updateAppShortcutParameters() dopo aver modificato i dati che alimentano i parametri. Il sistema bufferizza i suggerimenti; va invalidato esplicitamente.
  4. Usare @MainActor nel perform() di intent non-OpenIntent. Bloccherà il main thread di un'extension durante la lettura dati. Usa attori dedicati o ModelContext in background.
  5. Logging che fa crashare l'extension. Le App Intents extension hanno limiti di memoria stretti (intorno ai 30 MB). Niente print di dataset enormi, niente operazioni sincrone su grandi file.

Migrazione da SiriKit Intents Definition

Se hai un'app con i vecchi file .intentdefinition, Apple ha pubblicato una guida per la migrazione assistita: in Xcode 26 puoi cliccare con il tasto destro sul file e scegliere "Convert to App Intents". Funziona bene per gli intent semplici, meno per quelli con parametri custom o donate complesse al sistema. Il mio consiglio, dopo averlo fatto su un'app reale: usa la conversione automatica come scheletro, poi riscrivi le query e i parametri seguendo i pattern moderni di questo articolo. Il vecchio framework non è ancora deprecato ufficialmente, ma in iOS 26 ogni nuova feature di Apple Intelligence si appoggia esclusivamente alla nuova architettura. Fai un po' i conti.

FAQ

Qual è la differenza tra App Intent e App Shortcut?

Un App Intent è la definizione di un'azione (il "verbo" che la tua app sa eseguire), mentre un App Shortcut è un App Intent promosso a livello di sistema con una serie di frasi vocali, un'icona e un titolo. L'App Shortcut viene mostrato in Spotlight, nell'app Comandi rapidi e attivabile tramite Siri senza alcuna configurazione da parte dell'utente. Tutti gli App Shortcut sono App Intent, ma non viceversa: puoi avere App Intent privati usabili solo dal tuo widget o dalla tua estensione.

Servono iOS 26 e Xcode 26 per usare App Intents?

No, App Intents è disponibile da iOS 16 e Xcode 14. Tuttavia, le funzionalità descritte in questa guida — @ComputedProperty, @Dependency nelle EntityQuery, integrazione semantica Spotlight con embedding, .result(view:) per snippet SwiftUI inline e i Visual Intents — richiedono iOS 26 e Xcode 26. Per gli intent base (un'azione semplice senza dipendenze) il codice gira identico fin da iOS 16.

Posso usare App Intents senza SwiftData?

Assolutamente sì. SwiftData è un'opzione ergonomica perché si integra naturalmente con @Dependency, ma puoi alimentare le tue EntityQuery con Core Data, Realm, GRDB, file JSON, una API remota, o qualsiasi altra fonte dati. L'unico requisito è che l'id di ogni AppEntity sia stabile e univoco: il sistema può memorizzare riferimenti agli identificatori e richiedere l'entità corrispondente molto tempo dopo, anche se la tua app è stata terminata.

Apple Intelligence funziona offline con i miei App Intent?

Le funzionalità di base di Siri e Spotlight basate sugli App Intents funzionano completamente offline su tutti i dispositivi compatibili. Le funzionalità più avanzate di Apple Intelligence che concatenano automaticamente più intent o estraggono semanticamente parametri da query in linguaggio naturale richiedono dispositivi con Apple Intelligence abilitato (iPhone 15 Pro e successivi su iOS 26) e in alcuni casi possono usare Private Cloud Compute. Il tuo codice non cambia: sono il sistema operativo e l'hardware a determinare il livello di intelligence applicato all'invocazione.

Quante App Shortcut posso dichiarare?

Apple raccomanda un massimo di 10 App Shortcut per AppShortcutsProvider, e il sistema mostrerà solo le prime 10 nell'app Comandi rapidi. Esiste inoltre un limite duro di 1.000 frasi totali per app, calcolate moltiplicando il numero di frasi per ogni shortcut per il numero di opzioni di ciascun parametro enumerabile. Se ti avvicini al limite, considera di trasformare alcune shortcut in App Intent semplici (non promossi) e di lasciare che l'utente li attivi manualmente da Comandi rapidi.

Conclusione

App Intents non è "un altro framework Apple": è la API che decide se la tua app sopravvive nell'ecosistema Apple del 2026. La buona notizia è che, una volta capito il pattern AppEntity + EntityQuery + @Dependency + AppShortcut, l'integrazione di una funzione esistente richiede in media meno di un'ora di lavoro. La cattiva notizia è che farla bene — frasi naturali, idempotenza, query performanti, snippet SwiftUI puliti — separa le app che gli utenti sentono "intelligenti" da quelle che restano dentro l'icona.

Inizia con un singolo intent, esponilo come shortcut con tre frasi, testalo con Siri sul tuo dispositivo. Vedrai immediatamente il valore, te lo prometto. Da lì, espandi: SwiftData, Spotlight semantico, widget configurabili, snippet inline. Costruirai un'app che parla davvero il linguaggio dell'iOS moderno.

Sull'Autore Daniel Okafor

Daniel is a former Spotify iOS engineer (2019-2024) who worked on the Now Playing surface and the in-app podcast player. He shipped the SwiftUI rewrite of the lyrics view to over 600 million users and contributed several fixes upstream to swift-collections. His writing tends toward the unglamorous corners of iOS work: build-time regressions in Xcode 16, why SwiftData still isn't ready for production sync scenarios, and how to instrument a real app with os_signpost without drowning in noise. He spent two years before Spotify at a fintech startup in Berlin building a banking app on top of Solaris API. Daniel now freelances out of Lisbon and maintains a small open-source library for type-safe deep links in SwiftUI. He has 9 years of native iOS experience.