App Intents et Siri Shortcuts : automatisation iOS avancée 2026

Guide complet App Intents et Siri Shortcuts iOS 18+. Créez des actions Siri personnalisées, intégrez Apple Intelligence et automatisez votre app Swift en 2026.

App Intents et Siri Shortcuts pour automatisation iOS avancée avec Swift et Apple Intelligence

En 2026, avec Apple Intelligence et le framework App Intents, les applications iOS entrent dans une ère où l'intention utilisateur prime sur l'interface graphique. Les apps qui n'exposent pas d'intents deviennent invisibles dans un OS orienté IA. App Intents constitue la fondation permettant à Siri, Spotlight, les widgets et l'Action Button d'interagir avec les fonctionnalités applicatives.

Ce que couvre cet article

Cet article présente la création complète d'App Intents et Siri Shortcuts pour iOS 18+, depuis les concepts fondamentaux jusqu'à l'intégration avec Apple Intelligence et les App Intent Domains.

Comprendre le framework App Intents

Le framework App Intents, introduit avec iOS 16, modernise la construction des intents en Swift en remplaçant l'ancien SiriKit Intents framework. Cette architecture déclarative permet de créer des actions découvrables par le système : Spotlight, Shortcuts app, Siri et l'Action Button.

TaskIntent.swiftswift
import AppIntents

// Un AppIntent représente une action que l'utilisateur peut effectuer
struct CreateTaskIntent: AppIntent {
    // Titre affiché dans Shortcuts et Siri
    static var title: LocalizedStringResource = "Créer une tâche"

    // Description pour l'accessibilité et les suggestions
    static var description = IntentDescription(
        "Crée une nouvelle tâche dans l'application."
    )

    // Paramètre avec validation automatique
    @Parameter(title: "Titre de la tâche")
    var taskTitle: String

    // Paramètre optionnel avec valeur par défaut
    @Parameter(title: "Priorité", default: .medium)
    var priority: TaskPriority

    // Exécution de l'action
    func perform() async throws -> some IntentResult & ReturnsValue<TaskEntity> {
        // Création de la tâche via le service
        let task = TaskService.shared.createTask(
            title: taskTitle,
            priority: priority
        )

        // Retourne l'entité créée pour chaînage
        return .result(value: TaskEntity(task: task))
    }
}

L'intent déclare ses paramètres avec le property wrapper @Parameter, permettant à Siri de demander les valeurs manquantes. La méthode perform() exécute la logique métier et retourne un résultat typé.

Définir des App Entities pour les données

Les App Entities représentent les "noms" de l'application : les objets sur lesquels les intents opèrent. Elles permettent à Siri de comprendre et manipuler les données applicatives.

TaskEntity.swiftswift
import AppIntents

// Modèle de données interne
struct Task: Identifiable, Codable {
    let id: UUID
    var title: String
    var priority: TaskPriority
    var isCompleted: Bool
    var dueDate: Date?
}

// Entity exposée au système
struct TaskEntity: AppEntity {
    // Identifiant unique requis
    var id: UUID

    // Propriétés affichables
    var title: String
    var priority: TaskPriority
    var isCompleted: Bool

    // Configuration d'affichage dans le système
    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Tâche"

    // Représentation visuelle de l'instance
    var displayRepresentation: DisplayRepresentation {
        DisplayRepresentation(
            title: "\(title)",
            subtitle: "\(priority.rawValue)",
            image: .init(systemName: isCompleted ? "checkmark.circle.fill" : "circle")
        )
    }

    // Query par défaut pour rechercher des entités
    static var defaultQuery = TaskEntityQuery()

    // Initializer depuis le modèle interne
    init(task: Task) {
        self.id = task.id
        self.title = task.title
        self.priority = task.priority
        self.isCompleted = task.isCompleted
    }
}

// Query pour rechercher et filtrer les entités
struct TaskEntityQuery: EntityQuery {
    // Recherche par identifiants
    func entities(for identifiers: [UUID]) async throws -> [TaskEntity] {
        TaskService.shared.fetchTasks()
            .filter { identifiers.contains($0.id) }
            .map { TaskEntity(task: $0) }
    }

    // Suggestions affichées dans l'interface
    func suggestedEntities() async throws -> [TaskEntity] {
        TaskService.shared.fetchTasks()
            .filter { !$0.isCompleted }
            .prefix(5)
            .map { TaskEntity(task: $0) }
    }
}

L'EntityQuery définit comment le système recherche et suggère des entités. Les méthodes entities(for:) et suggestedEntities() alimentent les interfaces Siri et Shortcuts.

App Enums vs App Entities

Utilisez AppEnum pour les types avec un ensemble fixe de valeurs (priorité, statut), et AppEntity pour les types dynamiques créés par l'utilisateur (tâches, notes, contacts).

Créer des App Enums pour les valeurs fixes

Les App Enums exposent des types énumérés au système, permettant à Siri de proposer des choix contextuels.

TaskPriority.swiftswift
import AppIntents

// Enum exposé au système
enum TaskPriority: String, AppEnum, Codable {
    case low
    case medium
    case high

    // Nom du type affiché
    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Priorité"

    // Représentation de chaque cas
    static var caseDisplayRepresentations: [TaskPriority: DisplayRepresentation] = [
        .low: DisplayRepresentation(
            title: "Basse",
            image: .init(systemName: "arrow.down.circle")
        ),
        .medium: DisplayRepresentation(
            title: "Moyenne",
            image: .init(systemName: "minus.circle")
        ),
        .high: DisplayRepresentation(
            title: "Haute",
            image: .init(systemName: "exclamationmark.circle")
        )
    ]
}

// Enum pour le statut des tâches
enum TaskStatus: String, AppEnum, Codable {
    case pending
    case inProgress
    case completed

    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Statut"

    static var caseDisplayRepresentations: [TaskStatus: DisplayRepresentation] = [
        .pending: "En attente",
        .inProgress: "En cours",
        .completed: "Terminée"
    ]
}

Les représentations visuelles (icônes SF Symbols) enrichissent l'affichage dans Shortcuts et les suggestions Siri.

Implémenter l'AppShortcutsProvider

L'AppShortcutsProvider expose les App Shortcuts au système, les rendant immédiatement disponibles sans configuration utilisateur. Ces raccourcis apparaissent dans Spotlight, Siri et l'Action Button.

ShortcutsProvider.swiftswift
import AppIntents

// Provider déclarant tous les raccourcis de l'app
struct TaskAppShortcutsProvider: AppShortcutsProvider {
    // Maximum 10 raccourcis par application
    @AppShortcutsBuilder
    static var appShortcuts: [AppShortcut] {
        // Raccourci pour créer une tâche
        AppShortcut(
            intent: CreateTaskIntent(),
            phrases: [
                // Le placeholder .applicationName est OBLIGATOIRE
                "Créer une tâche avec \(.applicationName)",
                "Nouvelle tâche dans \(.applicationName)",
                "Ajouter une tâche à \(.applicationName)"
            ],
            shortTitle: "Créer tâche",
            systemImageName: "plus.circle"
        )

        // Raccourci pour lister les tâches
        AppShortcut(
            intent: ListTasksIntent(),
            phrases: [
                "Voir mes tâches dans \(.applicationName)",
                "Afficher les tâches de \(.applicationName)",
                "Quelles sont mes tâches \(.applicationName)"
            ],
            shortTitle: "Mes tâches",
            systemImageName: "list.bullet"
        )

        // Raccourci avec paramètre dynamique
        AppShortcut(
            intent: CompleteTaskIntent(),
            phrases: [
                "Terminer \(\.$taskName) dans \(.applicationName)",
                "Marquer \(\.$taskName) comme fait avec \(.applicationName)"
            ],
            shortTitle: "Terminer tâche",
            systemImageName: "checkmark.circle"
        )
    }
}

Les phrases vocales doivent inclure le placeholder \(.applicationName) pour que Siri identifie l'application cible. Les paramètres dynamiques comme \(\.$taskName) permettent des commandes contextuelles.

Limite de 10 raccourcis

Une application peut déclarer au maximum 10 App Shortcuts. Priorisez les actions les plus fréquentes et utiles pour les utilisateurs.

Intent avec paramètres complexes

Les intents peuvent accepter des paramètres complexes incluant des entités et des options de configuration.

CompleteTaskIntent.swiftswift
import AppIntents

struct CompleteTaskIntent: AppIntent {
    static var title: LocalizedStringResource = "Terminer une tâche"

    static var description = IntentDescription(
        "Marque une tâche comme terminée."
    )

    // Paramètre Entity avec recherche automatique
    @Parameter(title: "Tâche")
    var task: TaskEntity

    // Paramètre optionnel avec date
    @Parameter(title: "Date de complétion")
    var completionDate: Date?

    // Configuration du dialogue Siri
    static var parameterSummary: some ParameterSummary {
        Summary("Terminer \(\.$task)") {
            \.$completionDate
        }
    }

    func perform() async throws -> some IntentResult & ProvidesDialog {
        // Mise à jour de la tâche
        TaskService.shared.completeTask(
            id: task.id,
            completionDate: completionDate ?? Date()
        )

        // Feedback vocal pour Siri
        return .result(
            dialog: "La tâche \(task.title) a été marquée comme terminée."
        )
    }
}

// Intent retournant une liste d'entités
struct ListTasksIntent: AppIntent {
    static var title: LocalizedStringResource = "Lister les tâches"

    // Filtre optionnel
    @Parameter(title: "Statut", 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()

        // Application des filtres
        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)
    }
}

Le ParameterSummary définit comment Siri présente l'intent lors de l'exécution vocale, offrant un feedback naturel.

Prêt à réussir tes entretiens iOS ?

Entraîne-toi avec nos simulateurs interactifs, fiches express et tests techniques.

Intégration avec Apple Intelligence

iOS 18 introduit les App Intent Domains, des collections d'APIs conçues pour des fonctionnalités spécifiques. Ces domains permettent à Apple Intelligence de comprendre et d'exécuter des actions avec une précision accrue.

BookmarkDomainIntents.swiftswift
import AppIntents

// Conformité au domain Bookmarks pour intégration Apple Intelligence
struct SaveBookmarkIntent: AppIntent {
    static var title: LocalizedStringResource = "Sauvegarder un marque-page"

    // Paramètre URL validé automatiquement
    @Parameter(title: "URL")
    var url: URL

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

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

    // Ouverture de l'app si nécessaire
    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: "Marque-page sauvegardé : \(bookmark.title)"
        )
    }
}

// Intent avec awareness du contenu écran (iOS 18.4+)
struct AnalyzeScreenContentIntent: AppIntent {
    static var title: LocalizedStringResource = "Analyser le contenu"

    // Accès au contexte écran via Apple Intelligence
    @Parameter(title: "Contexte")
    var screenContext: String?

    func perform() async throws -> some IntentResult & ProvidesDialog {
        guard let context = screenContext else {
            return .result(dialog: "Aucun contenu à analyser.")
        }

        // Traitement du contenu extrait par Apple Intelligence
        let analysis = ContentAnalyzer.analyze(context)

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

Les domains prédéfinis (Books, Camera, Spreadsheets) permettent à Siri de répondre aux requêtes avec précision grâce aux modèles entraînés sur ces tâches spécifiques.

Actions Siri avec confirmation utilisateur

Pour les actions sensibles, le système peut demander une confirmation avant exécution.

DeleteTaskIntent.swiftswift
import AppIntents

struct DeleteTaskIntent: AppIntent {
    static var title: LocalizedStringResource = "Supprimer une tâche"

    @Parameter(title: "Tâche")
    var task: TaskEntity

    // Exige une confirmation utilisateur
    static var isDiscoverable: Bool = true

    func perform() async throws -> some IntentResult & ProvidesDialog {
        // Demande de confirmation via dialog
        try await requestConfirmation(
            result: .result(
                dialog: "Voulez-vous vraiment supprimer \(task.title) ?"
            )
        )

        // Suppression après confirmation
        TaskService.shared.deleteTask(id: task.id)

        return .result(
            dialog: "La tâche \(task.title) a été supprimée."
        )
    }
}

// Intent avec plusieurs étapes de dialog
struct ScheduleTaskIntent: AppIntent {
    static var title: LocalizedStringResource = "Planifier une tâche"

    @Parameter(title: "Tâche")
    var task: TaskEntity

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

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

    static var parameterSummary: some ParameterSummary {
        When(\.$setReminder, .equalTo, true) {
            Summary("Planifier \(\.$task) pour \(\.$scheduledDate) avec rappel")
        } otherwise: {
            Summary("Planifier \(\.$task) pour \(\.$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: "Tâche planifiée pour le \(formattedDate)."
        )
    }
}

La méthode requestConfirmation interrompt l'exécution jusqu'à validation utilisateur, protégeant contre les actions accidentelles.

Intents dans les widgets interactifs

Les App Intents s'intègrent naturellement avec WidgetKit pour créer des widgets interactifs iOS 17+.

TaskWidgetIntents.swiftswift
import AppIntents
import WidgetKit

// Intent optimisé pour widget (exécution rapide)
struct ToggleTaskFromWidgetIntent: AppIntent {
    static var title: LocalizedStringResource = "Basculer tâche"

    @Parameter(title: "ID Tâche")
    var taskID: String

    init() {}

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

    // Pas de dialog pour les widgets
    func perform() async throws -> some IntentResult {
        guard let uuid = UUID(uuidString: taskID) else {
            return .result()
        }

        TaskService.shared.toggleCompletion(taskId: uuid)

        // Rafraîchissement immédiat du widget
        WidgetCenter.shared.reloadTimelines(ofKind: "TaskWidget")

        return .result()
    }
}

// Vue widget avec bouton interactif
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)
    }
}

Les widgets utilisent la syntaxe Button(intent:) pour connecter directement l'interaction à l'App Intent, sans ouvrir l'application.

Configuration de l'Action Button

L'Action Button des iPhone 15 Pro et ultérieurs peut déclencher des App Shortcuts directement.

ActionButtonIntent.swiftswift
import AppIntents

// Intent optimisé pour Action Button
struct QuickCaptureIntent: AppIntent {
    static var title: LocalizedStringResource = "Capture rapide"

    static var description = IntentDescription(
        "Crée rapidement une tâche avec un titre."
    )

    // Ouvre l'app pour saisie
    static var openAppWhenRun: Bool = true

    func perform() async throws -> some IntentResult & OpensIntent {
        // Notification pour ouvrir l'écran de saisie rapide
        NotificationCenter.default.post(
            name: .quickCaptureTriggered,
            object: nil
        )

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

// Déclarer dans AppShortcutsProvider pour Action Button
extension TaskAppShortcutsProvider {
    @AppShortcutsBuilder
    static var appShortcuts: [AppShortcut] {
        // ... autres raccourcis

        AppShortcut(
            intent: QuickCaptureIntent(),
            phrases: [
                "Capture rapide \(.applicationName)",
                "Note rapide \(.applicationName)"
            ],
            shortTitle: "Capture",
            systemImageName: "bolt.circle"
        )
    }
}

L'utilisateur peut configurer l'Action Button pour déclencher ce raccourci via Réglages > Action Button.

Macros iOS 18+

iOS 18 introduit les macros @DeferredProperty et @ComputedProperty pour réduire le boilerplate. Les App Intents peuvent également résider dans des Swift Packages pour une réutilisation cross-platform.

Intents dans Swift Packages

Les App Intents peuvent être définis dans des Swift Packages pour partage entre iOS, macOS et 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 partagé cross-platform
public struct SharedCreateTaskIntent: AppIntent {
    public static var title: LocalizedStringResource = "Créer une tâche"

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

    public init() {}

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

// Extension plateforme spécifique dans l'app
#if os(iOS)
extension SharedCreateTaskIntent {
    // Comportement iOS spécifique
    static var openAppWhenRun: Bool = false
}
#endif

Cette architecture permet de maintenir une base de code unique pour les intents tout en adaptant le comportement par plateforme.

Tests des App Intents

Les App Intents peuvent être testés unitairement comme tout code Swift.

TaskIntentTests.swiftswift
import XCTest
import AppIntents
@testable import TaskApp

final class TaskIntentTests: XCTestCase {

    override func setUp() {
        super.setUp()
        // Reset du service pour tests isolés
        TaskService.shared.reset()
    }

    func testCreateTaskIntent() async throws {
        // Given
        var intent = CreateTaskIntent()
        intent.taskTitle = "Test Task"
        intent.priority = .high

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

        // Then
        let tasks = TaskService.shared.fetchTasks()
        XCTAssertEqual(tasks.count, 1)
        XCTAssertEqual(tasks.first?.title, "Test Task")
        XCTAssertEqual(tasks.first?.priority, .high)
    }

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

        let query = TaskEntityQuery()

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

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

Les tests vérifient le comportement des intents indépendamment de l'interface système.

Bonnes pratiques et optimisations

Plusieurs patterns garantissent des App Intents performants et une expérience utilisateur optimale.

BestPractices.swiftswift
import AppIntents

// 1. Phrases localisées
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. Gestion d'erreurs appropriée
enum TaskIntentError: Error, CustomLocalizedStringResourceConvertible {
    case taskNotFound
    case invalidInput
    case serviceUnavailable

    var localizedStringResource: LocalizedStringResource {
        switch self {
        case .taskNotFound:
            return "Tâche introuvable."
        case .invalidInput:
            return "Données invalides."
        case .serviceUnavailable:
            return "Service temporairement indisponible."
        }
    }
}

struct RobustTaskIntent: AppIntent {
    static var title: LocalizedStringResource = "Intent robuste"

    @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: "Tâche trouvée : \(task.title)")
    }
}

// 3. Optimisation des EntityQueries
struct OptimizedTaskQuery: EntityStringQuery {
    // Recherche textuelle optimisée
    func entities(matching string: String) async throws -> [TaskEntity] {
        // Recherche côté service avec limite
        TaskService.shared.search(query: string, limit: 10)
            .map { TaskEntity(task: $0) }
    }

    // Suggestions limitées pour performance
    func suggestedEntities() async throws -> [TaskEntity] {
        TaskService.shared.fetchRecentTasks(limit: 5)
            .map { TaskEntity(task: $0) }
    }
}

// 4. Focus Filter pour modes de concentration
struct TaskFocusFilter: SetFocusFilterIntent {
    static var title: LocalizedStringResource = "Filtrer les tâches"

    @Parameter(title: "Afficher uniquement priorité haute")
    var showHighPriorityOnly: Bool

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

Ces patterns assurent une intégration fluide avec le système tout en maintenant des performances optimales.

Conclusion

App Intents et Siri Shortcuts transforment la manière dont les utilisateurs interagissent avec les applications iOS. En 2026, avec Apple Intelligence, exposer des intents n'est plus optionnel mais essentiel pour offrir une expérience moderne et intuitive.

Checklist App Intents iOS 18+

  • ✅ Créer des AppIntents pour les actions principales de l'application
  • ✅ Définir des AppEntities pour les données manipulables
  • ✅ Utiliser AppEnum pour les types énumérés
  • ✅ Implémenter AppShortcutsProvider avec phrases vocales
  • ✅ Respecter la limite de 10 App Shortcuts maximum
  • ✅ Inclure \(.applicationName) dans toutes les phrases
  • ✅ Configurer les ParameterSummary pour feedback Siri
  • ✅ Intégrer les domains Apple Intelligence si applicable
  • ✅ Tester les intents avec des tests unitaires
  • ✅ Localiser les titres et descriptions

Passe à la pratique !

Teste tes connaissances avec nos simulateurs d'entretien et tests techniques.

Tags

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

Partager

Articles similaires