-
Découvrez les nouvelles avancées en matière dans App Intents
Découvrez toutes les nouveautés du framework App Intents dans les versions de cette année. Découvrez les améliorations visant à optimiser l'expérience des équipes de développement, comme les propriétés différées, les nouvelles fonctionnalités telles que les extraits de code interactifs d'App Intents, les annotations de vue d'entité, l'intégration de Visual Intelligence, et bien plus encore. Nous vous expliquerons comment App Intents est plus expressif que jamais, tout en étant encore plus facile et plus fluide à adopter. Nous vous présenterons également les nouveaux clients d'App Intents de cette année, comme Spotlight et Visual Intelligence, et apprendrons à écrire des app intents qui fonctionnent parfaitement dans ces contextes.
Chapitres
- 0:00 - Introduction
- 0:55 - Extraits interactifs
- 8:15 - Nouvelles intégrations de systèmes
- 15:01 - Améliorations de l’expérience utilisateur
- 21:02 - API de commodité
Ressources
- Adopting App Intents to support system experiences
- Building a workout app for iPhone and iPad
- Accelerating app interactions with App Intents
- App intent domains
- Creating your first app intent
- Integrating actions with Siri and Apple Intelligence
- Making actions and content discoverable and widely available
- PurchaseIntent
- App Shortcuts
- App Intents
Vidéos connexes
WWDC25
- Apprenez à connaître App Intents
- Concevoir des extraits interactifs
- Développez pour Raccourcis et Spotlight avec App Intents
WWDC24
-
Rechercher dans cette vidéo…
-
-
4:08 - Returning a Snippet Intent
import AppIntents import SwiftUI struct ClosestLandmarkIntent: AppIntent { static let title: LocalizedStringResource = "Find Closest Landmark" @Dependency var modelData: ModelData func perform() async throws -> some ReturnsValue<LandmarkEntity> & ShowsSnippetIntent & ProvidesDialog { let landmark = await self.findClosestLandmark() return .result( value: landmark, dialog: IntentDialog( full: "The closest landmark is \(landmark.name).", supporting: "\(landmark.name) is located in \(landmark.continent)." ), snippetIntent: LandmarkSnippetIntent(landmark: landmark) ) } } -
4:31 - Building a SnippetIntent
struct LandmarkSnippetIntent: SnippetIntent { static let title: LocalizedStringResource = "Landmark Snippet" @Parameter var landmark: LandmarkEntity @Dependency var modelData: ModelData func perform() async throws -> some IntentResult & ShowsSnippetView { let isFavorite = await modelData.isFavorite(landmark) return .result( view: LandmarkView(landmark: landmark, isFavorite: isFavorite) ) } } -
5:45 - Associate intents with buttons
struct LandmarkView: View { let landmark: LandmarkEntity let isFavorite: Bool var body: some View { // ... Button(intent: UpdateFavoritesIntent(landmark: landmark, isFavorite: !isFavorite)) { /* ... */ } Button(intent: FindTicketsIntent(landmark: landmark)) { /* ... */ } // ... } } -
6:53 - Request confirmation snippet
struct FindTicketsIntent: AppIntent { func perform() async throws -> some IntentResult & ShowsSnippetIntent { let searchRequest = await searchEngine.createRequest(landmarkEntity: landmark) // Present a snippet that allows people to change // the number of tickets. try await requestConfirmation( actionName: .search, snippetIntent: TicketRequestSnippetIntent(searchRequest: searchRequest) ) // Resume searching... } } -
7:24 - Using Entities as parameters
struct TicketRequestSnippetIntent: SnippetIntent { static let title: LocalizedStringResource = "Ticket Request Snippet" @Parameter var searchRequest: SearchRequestEntity func perform() async throws -> some IntentResult & ShowsSnippetView { let view = TicketRequestView(searchRequest: searchRequest) return .result(view: view) } } -
8:01 - Updating a snippet
func performRequest(request: SearchRequestEntity) async throws { // Set to pending status... TicketResultSnippetIntent.reload() // Kick off search... TicketResultSnippetIntent.reload() } -
9:24 - Responding to Image Search
struct LandmarkIntentValueQuery: IntentValueQuery { @Dependency var modelData: ModelData func values(for input: SemanticContentDescriptor) async throws -> [LandmarkEntity] { guard let pixelBuffer: CVReadOnlyPixelBuffer = input.pixelBuffer else { return [] } let landmarks = try await modelData.searchLandmarks(matching: pixelBuffer) return landmarks } } -
9:51 - Support opening an entity
struct OpenLandmarkIntent: OpenIntent { static var title: LocalizedStringResource = "Open Landmark" @Parameter(title: "Landmark") var target: LandmarkEntity func perform() async throws -> some IntentResult { /// ... } } -
10:53 - Show search results in app
@AppIntent(schema: .visualIntelligence.semanticContentSearch) struct ShowSearchResultsIntent { var semanticContent: SemanticContentDescriptor @Dependency var navigator: Navigator func perform() async throws -> some IntentResult { await navigator.showImageSearch(semanticContent.pixelBuffer) return .result() } // ... } -
11:40 - Returning multiple entity types
@UnionValue enum VisualSearchResult { case landmark(LandmarkEntity) case collection(CollectionEntity) }a struct LandmarkIntentValueQuery: IntentValueQuery { func values(for input: SemanticContentDescriptor) async throws -> [VisualSearchResult] { // ... } } struct OpenLandmarkIntent: OpenIntent { /* ... */ } struct OpenCollectionIntent: OpenIntent { /* ... */ } -
13:00 - Associating a view with an AppEntity
struct LandmarkDetailView: View { let landmark: LandmarkEntity var body: some View { Group{ /* ... */ } .userActivity("com.landmarks.ViewingLandmark") { activity in activity.title = "Viewing \(landmark.name)" activity.appEntityIdentifier = EntityIdentifier(for: landmark) } } } -
13:21 - Converting AppEntity to PDF
import CoreTransferable import PDFKit extension LandmarkEntity: Transferable { static var transferRepresentation: some TransferRepresentation { DataRepresentation(exportedContentType: .pdf) {landmark in // Create PDF data... return data } } } -
14:05 - Associating properties with Spotlight keys
struct LandmarkEntity: IndexedEntity { // ... @Property(indexingKey: \.displayName) var name: String @Property(customIndexingKey: /* ... */) var continent: String // ... } -
15:49 - Making intents undoable
struct DeleteCollectionIntent: UndoableIntent { // ... func perform() async throws -> some IntentResult { // Confirm deletion... await undoManager?.registerUndo(withTarget: modelData) {modelData in // Restore collection... } await undoManager?.setActionName("Delete \(collection.name)") // Delete collection... } } -
16:52 - Multiple choice
struct DeleteCollectionIntent: UndoableIntent { func perform() async throws -> some IntentResult & ReturnsValue<CollectionEntity?> { let archive = Option(title: "Archive", style: .default) let delete = Option(title: "Delete", style: .destructive) let resultChoice = try await requestChoice( between: [.cancel, archive, delete], dialog: "Do you want to archive or delete \(collection.name)?", view: collectionSnippetView(collection) ) switch resultChoice { case archive: // Archive collection... case delete: // Delete collection... default: // Do nothing... } } // ... } -
18:47 - Supported modes
struct GetCrowdStatusIntent: AppIntent { static let supportedModes: IntentModes = [.background, .foreground] func perform() async throws -> some ReturnsValue<Int> & ProvidesDialog { if systemContext.currentMode == .foreground { await navigator.navigateToCrowdStatus(landmark) } // Retrieve status and return dialog... } } -
19:30 - Supported modes
struct GetCrowdStatusIntent: AppIntent { static let supportedModes: IntentModes = [.background, .foreground(.dynamic)] func perform() async throws -> some ReturnsValue<Int> & ProvidesDialog { guard await modelData.isOpen(landmark) else { /* Exit early... */ } if systemContext.currentMode.canContinueInForeground { do { try await continueInForeground(alwaysConfirm: false) await navigator.navigateToCrowdStatus(landmark) } catch { // Open app denied. } } // Retrieve status and return dialog... } } -
21:30 - View Control
extension OpenLandmarkIntent: TargetContentProvidingIntent {} struct LandmarksNavigationStack: View { @State var path: [Landmark] = [] var body: some View { NavigationStack(path: $path) { /* ... */ } .onAppIntentExecution(OpenLandmarkIntent.self) { intent in self.path.append(intent.landmark) } } } -
23:13 - Scene activation condition
@main struct AppIntentsTravelTrackerApp: App { var body: some Scene { WindowGroup { /* ... */ } WindowGroup { /* ... */ } .handlesExternalEvents(matching: [ OpenLandmarkIntent.persistentIdentifier ]) } } -
23:33 - View activation condition
struct LandmarksNavigationStack: View { var body: some View { NavigationStack(path: $path) { /* ... */ } .handlesExternalEvents( preferring: [], allowing: !isEditing ? [OpenLandmarkIntent.persistentIdentifier] : [] ) } } -
24:23 - Computed property
struct SettingsEntity: UniqueAppEntity { @ComputedProperty var defaultPlace: PlaceDescriptor { UserDefaults.standard.defaultPlace } init() { } } -
24:48 - Deferred property
struct LandmarkEntity: IndexedEntity { // ... @DeferredProperty var crowdStatus: Int { get async throws { await modelData.getCrowdStatus(self) } } // ... } -
25:50 - AppIntentsPackage
// Framework or dynamic library public struct LandmarksKitPackage: AppIntentsPackage { } // App target struct LandmarksPackage: AppIntentsPackage { static var includedPackages: [any AppIntentsPackage.Type] { [LandmarksKitPackage.self] } }
-
-
- 0:00 - Introduction
App Intents est un framework qui permet aux apps d’intégrer des fonctionnalités sur différents appareils, telles que les raccourcis, Spotlight et l’intelligence visuelle. Découvrez de nouvelles interfaces interactives, l’intégration des apps à l’échelle du système, des expériences utilisateur améliorées et des API pratiques pour les développeurs.
- 0:55 - Extraits interactifs
Grâce aux dernières mises à jour, vous pouvez améliorer vos apps avec des interfaces interactives. Ces interfaces sont des vues dynamiques qui affichent des informations personnalisées en fonction des App Intents. Par exemple, une app de jardinage peut suggérer d’activer les arroseurs, ou une app de commande de repas peut permettre aux utilisateurs de configurer leurs commandes avant de les confirmer. L’app TravelTracking est un excellent exemple. Lorsqu’un utilisateur recherche le site touristique le plus proche, une interface interactive s’affiche. Il comprend le nom du site touristique et un bouton en forme de cœur. Après avoir appuyé sur ce bouton, l’utilisateur peut ajouter le site touristique à ses favoris. L’interface est alors instantanément mise à jour pour refléter le nouveau statut, sans quitter la vue actuelle. Cette interactivité est possible grâce au nouveau protocole « SnippetIntent ». Vous pouvez créer des interfaces pour les résultats qui affichent des informations après une action. Ces interfaces peuvent inclure des boutons ou des bascules qui déclenchent d’autres App Intents. Par exemple, dans l’app TravelTracking, le bouton en forme de cœur exécute l’Intent Mettre à jour les favoris et vous pouvez ajouter un bouton Rechercher des billets pour lancer une autre Intent. Lorsqu’un utilisateur interagit avec ces boutons, le système exécute l’Intent correspondante et l’interface est mise à jour en conséquence. Le système garantit que les données sont toujours récentes et à jour en récupérant les entités de l’app à partir des requêtes à chaque actualisation de l’interface. Ce processus transparent et animé offre une expérience utilisateur conviviale. Vous pouvez également créer des interfaces de confirmation qui demandent aux utilisateurs de fournir des informations supplémentaires avant de continuer. Par exemple, dans l’app TravelTracking, lorsqu’un utilisateur appuie sur Trouver des billets, une interface de confirmation s’affiche et lui demande combien de billets il souhaite rechercher. L’utilisateur peut alors interagir avec cette interface, et le système met à jour la vue en temps réel en fonction de ses entrées.
- 8:15 - Nouvelles intégrations de systèmes
App Intents dans iOS 26 améliore l’intégration du système et facilite la recherche d’images directement à partir des captures de l’appareil photo ou des captures d’écran. Les apps peuvent désormais afficher leurs résultats de recherche dans le panneau de recherche du système. Pour prendre en charge cette fonctionnalité, utilisez des requêtes compatibles avec le protocole « IntentValueQuery » pour traiter les données « SemanticContentDescriptor » afin de renvoyer des arrays d’entités d’app. Lorsqu’un utilisateur appuie sur un résultat, l’OpenIntent correspondant est déclenché, ce qui ouvre l’app sur la page pertinente. Les « OpenIntents » ne se limitent pas à la recherche d’images, vous pouvez également les utiliser dans Spotlight. Envisagez d’optimiser les performances de recherche, de renvoyer plusieurs pages de résultats et de permettre aux utilisateurs de poursuivre leur recherche dans l’app. Outre la recherche d’images, App Intents active les entités à l’écran, ce qui permet aux utilisateurs d’interagir avec Siri et ChatGPT au sujet du contenu visible dans l’app. Vous pouvez associer des entités à des vues, les rendre compatibles avec le protocole « Transferable » et gérer différents formats de données, comme les PDF, le texte simple ou le texte enrichi. Pour optimiser les résultats de Spotlight, adaptez les éléments de vos apps au format « IndexedEntity », intégrez-les à l’index Spotlight et enrichissez l’interface avec des annotations d’entités. Grâce à « PredictableIntent », le système pourra analyser les habitudes des utilisateurs afin de proposer des suggestions plus pertinentes et personnalisées.
- 15:01 - Améliorations de l’expérience utilisateur
Les nouvelles fonctionnalités de la plateforme de développement d’apps offrent une meilleure expérience aux utilisateurs, notamment grâce à un système d’annulation plus performant, des options de sélection multiple et différents modes de fonctionnement. Le protocole « UndoableIntent » permet aux utilisateurs d’annuler les actions effectuées avec App Intents. Les gestes familiers offrent ainsi plus de sécurité pour l’expérimentation. Vous pouvez intégrer cette fonctionnalité en adaptant leurs Intents au protocole et en enregistrant les actions d’annulation auprès du gestionnaire d’annulation. Grâce à l’API à choix multiples, vous pouvez proposer plusieurs options pour une Intent, plutôt qu’une simple confirmation binaire. Vous pouvez également personnaliser l’affichage à l’aide d’une boîte de dialogue et d’une vue SwiftUI personnalisée. Avec les Modes pris en charge, vos Intents peuvent mieux contrôler la mise en avant de l’app. Cette fonctionnalité permet aux Intents de se comporter différemment selon la manière dont un utilisateur interagit avec l’appareil. Par exemple, une Intent peut fournir des informations vocales uniquement lorsque l’utilisateur conduit. L’utilisateur est alors redirigé directement vers l’app lorsqu’il regarde l’appareil. Vous pouvez spécifier les modes pris en charge pour ses Intents et utiliser la propriété « currentMode » pour vérifier quel mode est actif.
- 21:02 - API de commodité
Grâce aux nouvelles API de contrôle de vue dans SwiftUI, vous pouvez refactoriser les App Intents en supprimant le code de l’interface utilisateur et en laissant les vues gérer directement la navigation. Utilisez le modificateur de vue « onAppIntentExecution », qui permet aux vues de répondre à des App Intents spécifiques et de modifier l’interface utilisateur en conséquence. Le système exécute la fermeture de l’action avant de ramener l’app au premier plan, et plusieurs vues peuvent répondre à la même Intent. Vous pouvez contrôler quelle scène gère une Intent à l’aide des API « handlesExternalEvents », ce qui garantit une navigation adaptée au contexte. De plus, de nouvelles macros, telles que « ComputedProperty » et « DeferredProperty » optimisent les « AppEntities ». Elles réduisent ainsi les coûts de stockage et d’instanciation. Les App Intents peuvent désormais être intégrées dans des Swift Packages. Elles offrent donc une plus grande flexibilité et une meilleure réutilisabilité.