
-
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
- Accelerating app interactions with App Intents
- Adopting App Intents to support system experiences
- App intent domains
- App Intents
- App Shortcuts
- Building a workout app for iPhone and iPad
- Creating your first app intent
- Integrating actions with Siri and Apple Intelligence
- Making actions and content discoverable and widely available
- PurchaseIntent
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…
Bonjour, je m’appelle Jeff et je suis un ingénieur de l’équipe App Intents. Aujourd’hui, nous allons examiner les nouvelles avancées d’App Intents.
App Intents vous permet d’intégrer les fonctionnalités de votre app aux appareils, y compris dans des éléments comme Raccourcis, Spotlight et l’intelligence visuelle.
Si vous ne connaissez pas ce framework, consultez d’abord la séance « Get to know App Intents », puis revenez me voir quand vous le voudrez. Je vais vous présenter les nouvelles expériences que vous pouvez créer avec les extraits interactifs, d’autres utilisations d’App Intents par votre app dans le système, des fonctionnalités pour affiner les expériences utilisateur de votre app et des API pratiques ajoutées pour améliorer votre expérience de développement. Commençons par les extraits interactifs.
Les extraits permettent à votre app d’afficher des vues personnalisées avec vos App Intents, pour demander une confirmation ou afficher un résultat. Vous pouvez désormais donner vie à ces extraits grâce à l’interactivité. Par exemple, vous pouvez créer un extrait de jardinage suggérant d’allumer l’arrosage automatique quand le sol est trop sec, personnaliser une commande avant de l’envoyer au restaurant ou encore intégrer votre app à des fonctionnalités système comme Activités en temps réel pour commencer à suivre un match immédiatement après avoir vérifié le score. Je vais vous faire une démonstration rapide d’un extrait interactif avec notre exemple d’app, puis nous explorerons sa mise en œuvre.
L’app TravelTracking contient de nombreux points d’intérêt à travers le monde. J’appuie sur la commande exécutant un App Intent pour trouver le point d’intérêt le plus proche.
Une fois qu’il est localisé, l’Intent présente un extrait affichant le point d’intérêt avec un cœur à côté du titre. Je vis à Toronto. Les chutes du Niagara sont presque dans mon jardin. Donc, je vais appuyer sur le cœur pour les ajouter à mes favoris.
L’extrait est immédiatement mis à jour pour afficher le nouvel état. C’est l’adoption du nouveau protocole Snippet Intent qui le permet. Ces Intents affichent les vues en fonction de leurs paramètres et de l’état de l’app. Vous pouvez les utiliser pour afficher des résultats après une action ou demander une confirmation. Voyons comment fonctionnent les extraits de résultats. Tout Intent de votre app peut renvoyer un Intent d’extrait avec des valeurs de paramètre dans son résultat. Ce processus est utilisé par le système chaque fois qu’il doit actualiser l’extrait. Lorsque cela se produit, le système renseigne les paramètres de l’Intent d’extrait avec les valeurs que vous avez spécifiées. Si certaines sont des entités d’app, elles sont récupérées à partir de leurs requêtes.
Ensuite, le système exécute la méthode perform de l’Intent d’extrait. L’Intent peut alors accéder à ses paramètres et à l’état de l’app pour afficher la vue, puis renvoyer cela dans le résultat. La vue peut associer des boutons à n’importe quel App Intent de votre app.
Dans l’exemple, le bouton en forme de cœur exécute l’Intent Mettre à jour les favoris et le bouton Rechercher des billets exécute l’Intent Rechercher des billets. Vous n’avez pas besoin de créer ces Intents uniquement pour les extraits. Vous pouvez en réutiliser des existants sans les modifier.
Quand un utilisateur tape sur le cœur, le système exécute l’Intent correspondant et attend qu’il ait terminé. Cela garantit que l’état des favoris est à jour. Une fois l’opération terminée, le système utilise l’Intent d’extrait paramétré que vous avez renvoyé précédemment pour déclencher une autre mise à jour. Comme précédemment, vous allez remplir les paramètres avec les valeurs que vous avez spécifiées. Les entités d’app seront également récupérées à partir de leurs requêtes. Ensuite, le système exécutera la méthode perform, donnant à l’Intent d’extrait une chance d’afficher une vue mise à jour. Dans ce cas, le cœur est maintenant plein. Toute modification de la vue sera animée selon les API contentTransition de SwiftUI. Le cycle se poursuit jusqu’à ce que l’extrait soit ignoré.
Mettons en œuvre le retour d’un Intent d’extrait. D’abord, je prends l’Intent existant et j’ajoute ShowsSnippetIntent à son type de retour. Ensuite, j’indique l’Intent d’extrait à l’aide du nouveau paramètre de la méthode result. Cela indique au système de remplir le paramètre avec le point d’intérêt donné chaque fois que l’Intent d’extrait est utilisé.
Maintenant, mettons en œuvre l’Intent d’extrait lui-même. D’abord, il doit être conforme au protocole SnippetIntent. Ensuite, j’ajoute les variables dont il a besoin pour afficher la vue correctement. Elles doivent être marquées comme paramètres, sinon elles ne seront pas renseignées par le système. Et comme il s’agit d’un App Intent, il a aussi accès aux AppDependencies. Dans le type de retour de la méthode perform, j’ajoute ShowsSnippetView. Et dans le corps, j’utilise des méthodes pour récupérer les états nécessaires à la production de la vue, puis je la retourne à l’aide du paramètre de vue dans une méthode result.
Comme pour les vues SwiftUI, votre Intent d’extrait sera créé et exécuté plusieurs fois durant son cycle de vie. Assurez-vous donc qu’il ne modifie pas l’état de l’app. Le système peut même l’exécuter plusieurs fois pour réagir aux changements de l’appareil, comme le passage en mode sombre. Veillez aussi à exécuter vos vues rapidement afin qu’elles ne soient pas perçues comme inactives.
Comme vous n’indiquez les valeurs de paramètre qu’une fois, vous devez uniquement les utiliser pour les entités d’app, interrogées chaque fois, et les valeurs primitives qui ne changent jamais. Toutes les autres valeurs doivent être récupérées dans la méthode perform.
L’extrait est créé. Voyons donc comment la vue SwiftUI déclenche les Intents.
Dans le corps de la vue, LandmarkView utilise l’initialiseur Button pour associer leurs App Intents correspondants. Des API similaires existent pour les boutons bascules.
Ces API ont été introduites avec des widgets interactifs aux côtés du modificateur contentTransition pour personnaliser les animations.
Si vous souhaitez en savoir plus, consultez la conférence de Luca de 2023. Maintenant, mettons en œuvre la fonctionnalité de recherche de billets via un extrait de confirmation.
Si j’appuie sur le bouton Trouver des billets, je devrai indiquer combien il m’en faut. Je vais augmenter à quatre, car des amis viennent aussi, puis je vais lancer la recherche. Le prix le moins cher sera affiché à l’aide d’un extrait de résultat. Passons chaque étape en revue. Commençons par l’extrait de résultat. Si vos boutons déclenchent des Intents qui présentent leurs propres extraits, ils remplaceront l’original. Attention : cela ne fonctionne que si l’original affichait un résultat. L’Intent de suivi peut présenter les deux types d’extraits, mais ici nous utilisons la confirmation de demande pour présenter la vue de configuration. Pour ce faire, il suffit d’appeler la méthode requestConfirmation. Vous pouvez personnaliser le nom de l’action et fournir l’Intent d’extrait qui pilote votre expérience de confirmation.
Cette méthode génèrera une erreur si l’extrait est annulé à tout moment. N’essayez pas de la capturer, laissez votre méthode perform se terminer.
Ensuite, chaque interaction avec la vue passera par le même cycle de mise à jour que l’extrait de résultat.
Dans l’exemple, l’Intent d’extrait affiche toujours le nombre de billets mis à jour. Comment fait-il cela ? Eh bien, comme j’ai modélisé la demande de recherche en tant qu’AppEntity, le système récupérera toujours la valeur la plus récente de la demande pour remplir ce paramètre. Je peux donc simplement la transmettre à ma vue et obtenir automatiquement les dernières valeurs.
Le système ne mettra pas fin à l’app tant que l’extrait sera visible. Vous pouvez donc conserver les états en mémoire. Inutile de les stocker dans une base de données.
Après toutes les interactions, lorsqu’un utilisateur tape enfin sur l’action de recherche, l’exécution App Intents originale reprend.
Parfois, vous voulez que votre extrait soit mis à jour au milieu d’une tâche, du fait d’un nouvel état.
Dans ces cas-là, appelez simplement la méthode de rechargement statique sur l’Intent d’extrait devant être mis à jour.
Consultez cette séance pour en savoir plus sur la conception des meilleurs extraits interactifs. Les extraits n’étaient qu’un début. Voyons d’autres façons dont les App Intents peuvent vous aider à intégrer votre app dans tout le système. La première est la recherche d’images. Cette fonctionnalité permet d’effectuer des recherches à partir d’une capture d’appareil photo ou d’écran. Nouveauté d’iOS 26, les résultats de recherche de votre app peuvent aussi apparaître. Ici, j’ai une capture d’écran d’un point d’intérêt. Je vais la sélectionner pour faire une recherche d’images, et le système va afficher un panneau de recherche. Je peux sélectionner l’app TravelTracking pour voir ses résultats. Et quand j’appuie sur un résultat, l’app s’ouvre sur la page du lieu correspondant.
Pour prendre en charge la recherche d’images, mettez en œuvre une requête conforme au protocole IntentValueQuery. Il accepte le type SemanticContentDescriptor en entrée et renvoie un tableau d’entités d’app. La recherche d’images affichera vos AppEntities à l’aide de leurs représentations d’affichage.
Quand un résultat est sélectionné, l’AppEntity correspondante est envoyée à son OpenIntent pour que votre app puisse le gérer. Cet OpenIntent doit exister, sinon votre app ne s’affichera pas.
Pour mettre en œuvre la requête, commencez par une structure conforme au protocole IntentValueQuery. La méthode values doit avoir SemanticContentDescriptor comme entrée. Celui-ci contient les pixels de la zone sélectionnée. Vous pouvez utiliser les API de Video Toolbox ou CoreImage pour le convertir en un type familier comme CGImage.
Après avoir effectué la recherche, renvoyez un tableau des entités correspondantes. Ensuite, mettez en œuvre un Intent conforme au protocole OpenIntent pour prendre en charge la sélection du résultat. Le paramètre cible doit avoir le même type d’entité que les résultats. Les OpenIntents ne servent pas seulement à prendre en charge la recherche d’images. Ils peuvent également être appelés à d’autres endroits, comme Spotlight, pour faciliter l’accès des utilisateurs à une entité de votre app.
L’expérience des utilisateurs sera meilleure s’ils peuvent vite trouver ce qu’ils cherchent. Renvoyez donc quelques pages de résultats pour que la correspondance parfaite soit plus susceptible d’apparaître dans l’interface du système. Vous pouvez utiliser vos serveurs pour interroger un ensemble de données plus volumineux. Cependant, ne cherchez pas trop longtemps. Sinon, l’app sera perçue comme inactive. Enfin, permettez aux utilisateurs de poursuivre leur recherche dans votre app s’ils ne trouvent pas ce qu’ils cherchent. Ajoutons cela à TravelTracking.
Si je ne vois pas le résultat attendu dans la liste, je peux appuyer sur le bouton Plus de résultats, qui ouvre l’app sur la vue de recherche. Pour mettre cela en œuvre, utilisez la nouvelle macro AppIntent pour spécifier le schéma semanticContentSearch. Cette nouvelle API remplace la macro AssistantIntent, car nous avons étendu les schémas à des fonctionnalités extérieures à l’assistant, comme l’intelligence visuelle. Ajoutez la propriété de contenu sémantique requise par le schéma. La macro le marquera automatiquement comme paramètre d’Intent.
Dans la méthode perform, traitez les métadonnées de recherche, puis accédez à la vue de recherche. Autre fonctionnalité : je veux que TravelTracking affiche les collections qui contiennent les points d’intérêt correspondants. Mais comment faire pour qu’une requête renvoie une combinaison de LandmarkEntity et CollectionEntity ? Réponse : en utilisant les UnionValues. D’abord, déclarez une UnionValue où chaque cas représente un type d’entité que la requête peut renvoyer. Ensuite, modifiez le type de résultat pour qu’il représente un tableau. Je peux également retourner un mélange des deux. Bien sûr, vous devez mettre en œuvre un OpenIntent pour chaque type d’entité. Pour en savoir plus sur les UnionValues, consultez la séance App Intents de Kenny de 2024.
C’est super que le contenu de votre app puisse être recherché en dehors, mais vous pouvez faire encore mieux avec Apple Intelligence lorsque votre app est active. Parlons des entités à l’écran. En utilisant NSUserActivities, votre app peut associer des entités au contenu à l’écran. Ainsi, les utilisateurs peuvent interroger ChatGPT sur des éléments actuellement visibles dans votre app.
Dans l’app TravelTracking, en regardant les chutes du Niagara, je peux demander à Siri si elles sont proches de l’océan. Siri comprendra que je fais référence à la vue à l’écran et proposera d’envoyer une capture d’écran à ChatGPT. Mais LandmarkEntity prend en charge les PDF, je vais donc plutôt choisir l’option de contenu complet. Je peux y jeter un œil, puis l’envoyer.
La réponse s’affichera et j’y apprendrai que les Grands Lacs ne sont pas un océan. Pour associer une entité à une vue, nous commençons par ajouter le modificateur userActivity à LandmarkDetailView. Ensuite, dans la fermeture de la mise à jour, associez un identifiant d’entité à l’activité. Il faut alors prendre en charge la conversion de LandmarkEntity en un type de données compréhensible par ChatGPT, comme un PDF.
Pour ce faire, il faut se conformer au protocole Transferable, puis fournir la représentation des données PDF. Les autres types pris en charge sont le texte brut et le texte enrichi. Consultez cette documentation App Intents pour en savoir plus sur les entités à l’écran.
Concernant la présentation du contenu de votre app au système, je veux parler brièvement de Spotlight. Maintenant que nous prenons en charge l’exécution d’actions directement depuis Spotlight sur Mac, App Intents peut vous permettre d’offrir la meilleure expérience possible.
D’abord, rendez les entités de votre app conformes à IndexedEntity et fournissez-les à Spotlight. Ainsi, la recherche Spotlight pilotera l’expérience de filtrage de vos paramètres. Pour associer des propriétés d’entité aux clés Spotlight, vous pouvez désormais utiliser le nouveau paramètre indexingKey sur l’attribut de propriété. Dans cet exemple, j’ai donné à « continent » une customIndexingKey. Cela permettra de rechercher des points d’intérêt asiatiques en tapant simplement « Asie » dans le champ de texte. L’adoption de l’API permet aussi à Raccourcis de générer automatiquement des actions de recherche pour vos entités.
Ensuite, annotez votre contenu à l’écran avec des entités, afin qu’elles s’affichent en priorité dans les suggestions lorsque leurs vues sont visibles. Enfin, mettez en œuvre PredictableIntent. Ainsi, le système apprendra et fournira des suggestions pour vos Intents selon les Intents et paramètres du comportement utilisateur précédent. Vous pouvez même fournir des descriptions personnalisées en fonction de leurs valeurs.
Si vous souhaitez en savoir plus sur cette fonctionnalité, consultez cette séance sur Raccourcis et Spotlight. Avec toutes ces fonctionnalités dans votre app, vous disposez de nouvelles façons d’affiner les expériences utilisateur. Commençons par la fonction Annuler. Les utilisateurs sont plus susceptibles d’essayer différentes possibilités s’ils savent qu’ils peuvent changer d’avis. C’est pourquoi il est important qu’ils puissent annuler les actions effectuées avec votre app. Avec le nouveau protocole UndoableIntent, vous pouvez facilement autoriser l’annulation de vos App Intents via des gestes familiers.
Voici quelques-unes de mes collections dans l’app TravelTracking. Je vais utiliser Écrire à Siri pour exécuter l’un de mes raccourcis personnalisés afin de supprimer la collection Sweet Deserts. Une fois la suppression confirmée, la collection est retirée de l’app. Maintenant, si je change d’avis, je peux balayer vers la gauche avec trois doigts pour déclencher l’annulation et restaurer la collection.
DeleteCollectionIntent participe à la pile d’annulation en se conformant d’abord au protocole UndoableIntent, qui fournit une propriété undoManager facultative avec laquelle vous pouvez enregistrer les actions d’annulation. D’autres opérations comme setActionName sont également disponibles. Le système attribue à votre Intent le gestionnaire d’annulation le plus pertinent via cette propriété, même quand ces Intents sont exécutés dans vos extensions. Cela garantit que les actions d’annulation de votre app dans l’interface utilisateur et les App Intents restent synchronisées et peuvent être annulées dans le bon ordre. Peaufinons encore cet Intent en proposant des alternatives à la suppression.
Nous pouvons utiliser la nouvelle API à choix multiples pour proposer plusieurs options aux utilisateurs. Je vais réexécuter l’Intent qui supprime la collection. Cette fois, au lieu de simplement me demander de confirmer la suppression, il fournit aussi une option d’archivage.
Hmm, cette collection me rappelle des souvenirs. Je vais plutôt l’archiver. Dans la méthode perform, présentez l’extrait à choix multiples en appelant la méthode requestChoice et fournissez un tableau d’options à l’aide du paramètre between. Les options peuvent être créées avec des titres personnalisés. Vous pouvez également spécifier un style qui indique au système comment rendre le tout.
Puis vous pouvez personnaliser l’extrait avec une boîte de dialogue et une vue SwiftUI personnalisée.
Lorsqu’une option est sélectionnée, elle est renvoyée à partir de la méthode requestChoice, à l’exception de l’annulation qui génère une erreur mettant fin à la méthode perform. Inutile de capturer cette erreur. Si une demande est annulée, l’Intent doit cesser immédiatement.
Ensuite, pour le suivi, utilisez une instruction switch pour créer une branche sur l’option choisie. Vous pouvez utiliser les options d’origine créées comme valeurs attendues. Parlons d’une autre amélioration possible. Avec les Modes pris en charge, vos Intents peuvent mieux contrôler la mise en avant de l’app. Cela leur permet de s’adapter à la façon dont l’utilisateur interagit avec l’appareil. Par exemple, si je conduis, je veux que les Intents me donnent des informations vocales. Mais si je regarde l’appareil, ils doivent plutôt m’amener dans l’app, car elle contient plus d’informations. Avec cette fonctionnalité, je peux mettre en œuvre un App Intent qui fait ces deux choses. Je vais vous montrer.
Voici l’Intent pour connaître l’affluence des chutes du Niagara.
Je vais désactiver l’option Ouvrir lors de l’exécution. Cela ressemble aux scénarios où la mise en avant n’est pas possible, comme l’utilisation de Siri avec un casque ou des écouteurs. Lors de l’exécution, seule une boîte de dialogue s’affiche, qui peut aussi être lue par Siri.
Mais si je désactive la boîte de dialogue et que j’active Ouvrir lors de l’exécution, j’arrive directement à la page du lieu qui montre l’affluence et la météo actuelle.
Ajoutons cette fonctionnalité à l’Intent.
D’abord, j’ajoute les modes pris en charge avec la variable statique. Si je la définis sur Arrière-plan uniquement, le système sait que l’app ne sera jamais mise en avant par l’Intent. Cependant, je veux aussi que l’Intent me dirige vers l’app si possible. J’ajoute donc le mode premier plan qui demande au système de lancer l’app avant d’exécuter l’Intent.
Avec la nouvelle propriété currentMode, je peux vérifier si nous sommes au premier plan. Si c’est le cas, nous pouvons naviguer en conséquence. Hmm, attendez une minute. Pourquoi l’affluence est-elle à zéro ? Oh, parce que c’est fermé. Modifions l’Intent pour qu’il n’ouvre pas l’app dans ce cas. Dans la méthode perform, je peux vérifier si le lieu est ouvert et arrêter s’il est fermé. Dans ce cas, je ne veux pas que l’app soit mise au premier plan avant l’exécution de l’Intent. Je vais donc modifier le mode premier plan pour qu’il soit dynamique. Les modes pris en charge disponibles incluent l’arrière-plan et trois modes premier plan. Le mode Immédiat indique au système de mettre l’app au premier plan avant d’exécuter l’Intent. Le mode Dynamique permet à l’Intent de décider de lancer l’app ou non. Et le mode Différé indique que l’Intent mettra l’app au premier plan, mais pas immédiatement. GetCrowdStatus peut ne pas lancer l’app, donc le mode Dynamique est idéal.
Pour ces deux modes, l’Intent peut utiliser la méthode continueInForeground pour contrôler exactement quand mettre l’app en avant.
Si nous sommes sûrs que le lieu est ouvert, nous pouvons utiliser la nouvelle propriété sur systemContext pour vérifier si nous pouvons mettre l’app au premier plan. Si nous le pouvons, nous appellerons la méthode continueInForeground pour ce faire. En définissant alwaysConfirm sur false, le système ne vous demandera pas s’il y a eu une activité au cours des dernières secondes. Si le lancement de l’app a réussi, nous pouvons enfin accéder à la vue de l’affluence. Toutefois, si la demande de lancement est refusée, par le système ou l’utilisateur, la méthode génère une erreur. Vous pouvez la capturer et la gérer en conséquence. Avant de conclure, je voudrais vous expliquer comment nous vous avons facilité le développement avec App Intents.
Pour que vos App Intents puissent naviguer dans l’interface utilisateur, il leur faut l’accès à tous les états de l’app qui pilotent les vues. Seuls des objets partagés globalement, comme AppDependencies, ou singletons le permettent. Mais avec les nouvelles API de contrôle de vue, vous pouvez supprimer le code UI des App Intents et laisser les vues le gérer elles-mêmes. Utilisons cette solution pour refactoriser notre Intent de point d’intérêt ouvert. D’abord, l’Intent doit être conforme au protocole TargetContentProvidingIntent. Dans la vue SwiftUI, une propriété path est utilisée pour modifier NavigationStack par programmation. Marquée comme un état, elle n’est accessible qu’à partir du corps de la vue. C’est là qu’intervient le modificateur de vue onAppIntentExecution. Il prend le type d’App Intent que vous voulez gérer, et une fermeture d’action avec l’Intent transmis. À l’intérieur, je peux référencer les paramètres de l’Intent et modifier l’interface utilisateur en conséquence. Une fois ce code en place, nous pouvons supprimer le code UI de l’Intent lui-même, ou supprimer entièrement la méthode perform, ainsi que la dépendance devenue inutile. C’est beau, non ?
Le système exécute votre fermeture d’action peu avant de mettre l’app au premier plan. Par conséquent, vous ne pouvez lire que les valeurs des paramètres de l’Intent. Toutes les autres opérations comme la demande de valeurs ne sont pas prises en charge. Et si plusieurs vues ont le même modificateur, elles seront toutes exécutées. Donc, chaque vue peut répondre en conséquence. Si votre app prend en charge plusieurs scènes ou fenêtres, vous pouvez contrôler laquelle exécute un Intent particulier. Par exemple, lorsque je modifie une collection dans l’app TravelTracking, je ne veux pas qu’elle navigue vers un point d’intérêt avec cette scène. Ce serait très perturbant. Heureusement, je peux contrôler cela à l’aide des API handlesExternalEvents. Un TargetContentProvidingIntent possède la propriété contentIdentifier, qui utilise par défaut son persistentIdentifier. Généralement, c’est le nom de structure de l’Intent. Vous pouvez aussi personnaliser cette valeur pour plus de précision.
Nous pouvons l’utiliser avec les modificateurs HandlesExternalEvents sur les scènes pour définir la condition d’activation, qui indique au système d’utiliser la scène pour exécuter l’App Intent. Il en créera un s’il n’existe pas déjà. Assurez-vous que l’identifiant du tableau correspond à la propriété contentIdentifier de l’Intent que vous voulez gérer. Mais si les conditions d’activation sont dynamiques, vous pouvez utiliser le même modificateur sur les vues. Ici, je n’autorise la scène à gérer OpenLandmarkIntent que si nous ne modifions pas une collection. Pour en savoir plus sur les scènes SwiftUI et les conditions d’activation, voici deux séances. Pour UIKit, les Intents doivent se conformer au protocole UISceneAppIntent, ce qui leur permet d’accéder à UIScene avec ces membres. Ou votre délégué de scène peut répondre à une exécution d’Intent après s’être conformé à AppIntentSceneDelegate. Enfin, vous pouvez aussi utiliser des conditions d’activation pour décider quelle scène gérera un App Intent. L’amélioration suivante est la nouvelle macro ComputedProperty qui permet d’éviter de stocker des valeurs sur les AppEntities. Voici ma SettingsEntity qui copie le defaultPlace à partir de UserDefaults. Je veux éviter de stocker une valeur en double sur la structure. Elle devrait plutôt être dérivée directement de la source de la vérité. Avec la nouvelle macro ComputedProperty, je peux y parvenir en accédant à UserDefaults directement à partir du getter.
Nous avons aussi ajouté une macro DeferredProperty pour réduire le coût d’instanciation d’une AppEntity. Dans LandmarkEntity, il existe une propriété crowdStatus dont la valeur provient d’un serveur réseau. Donc, sa récupération est relativement coûteuse. Je ne veux la récupérer que si le système le demande explicitement.
En marquant cette propriété comme DeferredProperty, je peux fournir un getter asynchrone. À l’intérieur, je peux appeler des méthodes qui effectuent les appels réseau. Ce getter asynchrone ne sera appelé que si des fonctionnalités système comme Raccourcis le demandent, pas lorsque LandmarkEntity est créée et transmise.
Voici quelques différences clés entre les trois types de propriétés. Choisissez ComputedProperty plutôt que Deferred, car sa surcharge système est plus faible. Ne revenez à Deferred que si cette propriété est trop coûteuse à calculer. Enfin, vous pouvez désormais mettre vos App Intents dans des packages Swift.
Vous pouvez déjà packager votre code AppIntents avec des frameworks et des bibliothèques dynamiques. Désormais, vous pouvez le placer dans des packages Swift et des bibliothèques statiques. Pour en savoir plus sur l’utilisation du protocole AppIntentsPackage, consultez la séance « Get to know App Intents ». J’espère que vous avez apprécié de découvrir ces fonctionnalités avec moi. Maintenant, essayez les extraits interactifs pour voir ce qu’ils peuvent faire pour vos Intents. Associez des entités au contenu à l’écran pour que le système puisse automatiquement les suggérer à l’utilisateur. Proposez plus d’options aux utilisateurs à l’aide de l’API à choix multiples. Prenez en charge plusieurs modes pour que vos Intents offrent la meilleure expérience possible, quel que soit leur mode d’exécution. Enfin, pour approfondir le code, consultez notre exemple d’app sur le site web des développeurs. Je suis ravi d’avoir enfin partagé tout cela avec vous. J’ai hâte de voir toutes les idées créatives que vous trouverez.
C’est tout pour l’instant. Merci de votre attention !
-
-
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é.