# 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. - Published: 2026-03-08 - Updated: 2026-03-28 - Author: SharpSkill - Tags: app-intents, siri-shortcuts, ios, swift, apple-intelligence - Reading time: 15 min --- 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. ```swift // TaskIntent.swift 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 { // 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. ```swift // TaskEntity.swift 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. ```swift // TaskPriority.swift 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. ```swift // ShortcutsProvider.swift 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. ```swift // CompleteTaskIntent.swift 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. ## 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. ```swift // BookmarkDomainIntents.swift 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. ```swift // DeleteTaskIntent.swift 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+. ```swift // TaskWidgetIntents.swift 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. ```swift // ActionButtonIntent.swift 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. ```swift // Package.swift // 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. ```swift // TaskIntentTests.swift 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. ```swift // BestPractices.swift 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 --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/fr/blog/ios/app-intents-siri-shortcuts-automatisation-ios-avancee