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.

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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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+.
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.
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.
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-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
}
#endifCette 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.
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.
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
Partager
Articles similaires

WidgetKit iOS 17+ : créer des widgets interactifs avec App Intents
Guide complet pour créer des widgets iOS interactifs avec WidgetKit et App Intents. Boutons, toggles, animations et bonnes pratiques pour iOS 17+ en 2026.

Combine vs async/await en Swift : patterns de migration progressive
Guide complet sur la migration de Combine vers async/await en Swift : stratégies progressives, bridging patterns, et coexistence des deux paradigmes dans une codebase iOS.

Questions entretien iOS accessibilité en 2026 : VoiceOver et Dynamic Type
Préparez vos entretiens iOS avec les questions clés sur l'accessibilité : VoiceOver, Dynamic Type, traits sémantiques et audits d'accessibilité.