Les intentions de SiriKit s'adapteront-elles à votre application ? Si oui, voici comment les utiliser
Publié: 2022-03-10Depuis iOS 5, Siri aide les utilisateurs d'iPhone à envoyer des messages, à définir des rappels et à rechercher des restaurants avec les applications d'Apple. À partir d'iOS 10, nous avons également pu utiliser Siri dans certaines de nos propres applications.
Pour utiliser cette fonctionnalité, votre application doit correspondre aux "domaines et intentions" Siri prédéfinis d'Apple. Dans cet article, nous allons découvrir ce que c'est et voir si nos applications peuvent les utiliser. Nous prendrons une application simple qui est un gestionnaire de liste de tâches et apprendrons comment ajouter la prise en charge de Siri. Nous passerons également en revue les directives du site Web des développeurs Apple sur la configuration et le code Swift pour un nouveau type d'extension qui a été introduit avec SiriKit : l'extension Intents .
Lorsque vous arriverez à la partie codage de cet article, vous aurez besoin de Xcode (au moins la version 9.x), et ce serait bien si vous êtes familier avec le développement iOS dans Swift car nous allons ajouter Siri à un petit travail application. Nous allons passer par les étapes de configuration d'une extension sur le site Web des développeurs d'Apple et d'ajout du code d'extension Siri à l'application.
"Hey Siri, pourquoi ai-je besoin de toi?"
Parfois, j'utilise mon téléphone sur mon canapé, les deux mains libres, et je peux accorder toute mon attention à l'écran. Peut-être que j'enverrai un texto à ma sœur pour planifier l'anniversaire de notre mère ou répondre à une question dans Trello. Je peux voir l'application. Je peux appuyer sur l'écran. Je peux taper.
Mais je suis peut-être en train de me promener dans ma ville, en écoutant un podcast, quand un SMS arrive sur ma montre. Mon téléphone est dans ma poche et je ne peux pas facilement répondre en marchant.
Avec Siri, je peux maintenir enfoncé le bouton de contrôle de mon casque et dire : « Envoyez un texto à ma sœur pour lui dire que je serai là à deux heures. Siri est idéal lorsque vous êtes en déplacement et que vous ne pouvez pas accorder toute votre attention à votre téléphone ou lorsque l'interaction est mineure, mais cela nécessite plusieurs tapotements et beaucoup de frappe.
C'est bien si je veux utiliser les applications Apple pour ces interactions. Mais certaines catégories d'applications, comme la messagerie, ont des alternatives très populaires. D'autres activités, telles que la réservation d'un trajet ou la réservation d'une table dans un restaurant, ne sont même pas possibles avec les applications intégrées d'Apple, mais sont parfaites pour Siri.
L'approche d'Apple en matière d'assistants vocaux
Pour activer Siri dans les applications tierces, Apple a dû décider d'un mécanisme pour prendre le son de la voix de l'utilisateur et le transmettre d'une manière ou d'une autre à l'application de manière à ce qu'elle puisse répondre à la demande. Pour rendre cela possible, Apple demande à l'utilisateur de mentionner le nom de l'application dans la demande, mais ils avaient plusieurs options pour savoir quoi faire avec le reste de la demande.
- Il aurait pu envoyer un fichier son à l'application.
L'avantage de cette approche est que l'application pourrait essayer de gérer littéralement toute demande que l'utilisateur pourrait avoir. Amazon ou Google auraient peut-être apprécié cette approche car ils disposent déjà de services sophistiqués de reconnaissance vocale. Mais la plupart des applications ne pourraient pas gérer cela très facilement. - Il aurait pu transformer le discours en texte et l'envoyer.
Étant donné que de nombreuses applications n'ont pas d'implémentations sophistiquées en langage naturel, l'utilisateur doit généralement s'en tenir à des phrases très particulières, et la prise en charge non anglaise incombe au développeur de l'application. - Il aurait pu vous demander de fournir une liste de phrases que vous comprenez.
Ce mécanisme est plus proche de ce qu'Amazon fait avec Alexa (dans son cadre de "compétences"), et il permet beaucoup plus d'utilisations d'Alexa que SiriKit ne peut actuellement en gérer. Dans une compétence Alexa, vous fournissez des phrases avec des variables d'espace réservé qu'Alexa remplira pour vous. Par exemple, « Alexa, rappelle-moi à$TIME$
to$REMINDER$
» — Alexa exécutera cette phrase par rapport à ce que l'utilisateur a dit et vous indiquera les valeurs deTIME
etREMINDER
. Comme avec le mécanisme précédent, le développeur doit faire toute la traduction, et il n'y a pas beaucoup de flexibilité si l'utilisateur dit quelque chose de légèrement différent. - Il pourrait définir une liste de requêtes avec des paramètres et envoyer à l'application une requête structurée.
C'est en fait ce que fait Apple, et l'avantage est qu'il peut prendre en charge une variété de langues, et il fait tout le travail pour essayer de comprendre toutes les façons dont un utilisateur peut formuler une demande. Le gros inconvénient est que vous ne pouvez implémenter des gestionnaires que pour les demandes définies par Apple. C'est très bien si vous avez, par exemple, une application de messagerie, mais si vous avez un service de streaming musical ou un lecteur de podcast, vous n'avez aucun moyen d'utiliser SiriKit pour le moment.
De même, il existe trois façons pour les applications de répondre à l'utilisateur : avec du son, avec du texte qui est converti, ou en exprimant le genre de chose que vous voulez dire et en laissant le système trouver la manière exacte de l'exprimer. La dernière solution (ce que fait Apple) impose la charge de la traduction à Apple, mais elle vous donne des moyens limités d'utiliser vos propres mots pour décrire les choses.
Les types de requêtes que vous pouvez gérer sont définis dans les domaines et les intentions de SiriKit. Une intention est un type de demande qu'un utilisateur peut faire, comme envoyer un SMS à un contact ou trouver une photo. Chaque intention a une liste de paramètres - par exemple, l'envoi de SMS nécessite un contact et un message.
Un domaine est juste un groupe d'intentions liées. La lecture d'un texte et l'envoi d'un texte relèvent tous deux du domaine de la messagerie. La réservation d'un trajet et l'obtention d'un emplacement relèvent du domaine de la réservation de trajet. Il existe des domaines pour passer des appels VoIP, démarrer des entraînements, rechercher des photos et quelques autres choses. La documentation de SiriKit contient une liste complète des domaines et de leurs intentions.
Une critique courante de Siri est qu'il semble incapable de gérer les demandes aussi bien que Google et Alexa, et que l'écosystème vocal tiers activé par les concurrents d'Apple est plus riche.
Je suis d'accord avec ces critiques. Si votre application ne correspond pas aux intentions actuelles, vous ne pouvez pas utiliser SiriKit et vous ne pouvez rien faire. Même si votre application convient, vous ne pouvez pas contrôler tous les mots que Siri dit ou comprend ; donc, si vous avez une façon particulière de parler des choses dans votre application, vous ne pouvez pas toujours l'enseigner à Siri.
L'espoir des développeurs iOS est à la fois qu'Apple élargisse considérablement sa liste d'intentions et que son traitement du langage naturel devienne bien meilleur. Si c'est le cas, nous aurons alors un assistant vocal qui fonctionnera sans que les développeurs aient à faire de traduction ou à comprendre toutes les façons de dire la même chose. Et la mise en œuvre de la prise en charge des requêtes structurées est en fait assez simple à faire - beaucoup plus facile que de créer un analyseur de langage naturel.
Un autre grand avantage du cadre d'intentions est qu'il ne se limite pas à Siri et aux demandes vocales. Même maintenant, l'application Maps peut générer une demande basée sur les intentions de votre application (par exemple, une réservation de restaurant). Il le fait par programmation (pas à partir de la voix ou du langage naturel). Si Apple autorisait les applications à découvrir les intentions exposées les unes des autres, nous aurions une bien meilleure façon pour les applications de travailler ensemble (par opposition aux URL de style x-callback).
Enfin, comme une intention est une requête structurée avec des paramètres, il existe un moyen simple pour une application d'exprimer que des paramètres sont manquants ou qu'elle a besoin d'aide pour distinguer certaines options. Siri peut ensuite poser des questions de suivi pour résoudre les paramètres sans que l'application ait besoin de mener la conversation.
Le domaine de réservation de trajets
Pour comprendre les domaines et les intentions, examinons le domaine de la réservation de trajets. C'est le domaine que vous utiliseriez pour demander à Siri de vous procurer une voiture Lyft.
Apple définit comment demander un trajet et comment obtenir des informations à ce sujet, mais il n'y a en fait aucune application Apple intégrée qui peut réellement gérer cette demande. C'est l'un des rares domaines où une application compatible SiriKit est requise.
Vous pouvez invoquer l'une des intentions via la voix ou directement depuis Maps. Certaines des intentions pour ce domaine sont :
- Demander un trajet
Utilisez celui-ci pour réserver une course. Vous devrez fournir un lieu de prise en charge et de dépose, et l'application devra peut-être également connaître la taille de votre groupe et le type de trajet que vous souhaitez. Un exemple de phrase pourrait être : "Réservez-moi une course avec <appname>". - Obtenir le statut du trajet
Utilisez cette intention pour savoir si votre demande a été reçue et pour obtenir des informations sur le véhicule et le conducteur, y compris leur emplacement. L'application Maps utilise cette intention pour afficher une image mise à jour de la voiture lorsqu'elle s'approche de vous. - Annuler un trajet
Utilisez-le pour annuler un trajet que vous avez réservé.
Pour chacune de ces intentions, Siri peut avoir besoin de plus d'informations. Comme vous le verrez lorsque nous implémenterons un gestionnaire d'intention, votre extension Intents peut indiquer à Siri qu'un paramètre requis est manquant, et Siri le demandera à l'utilisateur.
Le fait que les intentions puissent être invoquées par programmation par Maps montre comment les intentions pourraient permettre la communication inter-applications à l'avenir.
Remarque : Vous pouvez obtenir une liste complète des domaines et de leurs intentions sur le site Web des développeurs d'Apple. Il existe également un exemple d'application Apple avec de nombreux domaines et intentions mis en œuvre, y compris la réservation de trajets.
Ajout de la prise en charge du domaine des listes et des notes à votre application
OK, maintenant que nous comprenons les bases de SiriKit, regardons comment ajouter la prise en charge de Siri dans une application qui implique beaucoup de configuration et une classe pour chaque intention que vous souhaitez gérer.
Le reste de cet article comprend les étapes détaillées pour ajouter la prise en charge de Siri à une application. Il y a cinq choses de haut niveau que vous devez faire :
- Préparez-vous à ajouter une nouvelle extension à l'application en créant des profils d'approvisionnement avec de nouveaux droits pour celle-ci sur le site Web des développeurs d'Apple.
- Configurez votre application (via son
plist
) pour utiliser les droits. - Utilisez le modèle de Xcode pour commencer avec un exemple de code.
- Ajoutez le code pour prendre en charge votre intention Siri.
- Configurez le vocabulaire de Siri via
plist
s.
Ne vous inquiétez pas : nous passerons en revue chacun d'entre eux, en expliquant les extensions et les droits en cours de route.
Pour me concentrer uniquement sur les parties de Siri, j'ai préparé un simple gestionnaire de liste de tâches, List-o-Mat.
Vous pouvez trouver la source complète de l'exemple, List-o-Mat, sur GitHub.
Pour le créer, tout ce que j'ai fait a été de commencer avec le modèle d'application Xcode Master-Detail et de transformer les deux écrans en un UITableView
. J'ai ajouté un moyen d'ajouter et de supprimer des listes et des éléments, et un moyen de cocher les éléments comme terminé. Toute la navigation est générée par le modèle.
Pour stocker les données, j'ai utilisé le protocole Codable
(introduit à la WWDC 2017), qui transforme les structures en JSON et les enregistre dans un fichier texte dans le dossier des documents
.
J'ai délibérément gardé le code très simple. Si vous avez de l'expérience avec Swift et la création de contrôleurs de vue, cela ne devrait pas vous poser de problème.
Nous pouvons maintenant passer par les étapes d'ajout de la prise en charge de SiriKit. Les étapes de haut niveau seraient les mêmes pour n'importe quelle application et quel que soit le domaine et les intentions que vous prévoyez de mettre en œuvre. Nous traiterons principalement du site Web des développeurs d'Apple, en éditant des plist
et en écrivant un peu de Swift.
Pour List-o-Mat, nous nous concentrerons sur le domaine des listes et des notes, qui s'applique largement à des éléments tels que les applications de prise de notes et les listes de tâches.
Dans le domaine des listes et des notes, nous avons les intentions suivantes qui auraient du sens pour notre application.
- Obtenez une liste de tâches.
- Ajouter une nouvelle tâche à une liste.
Étant donné que les interactions avec Siri se produisent en dehors de votre application (peut-être même lorsque votre application n'est pas en cours d'exécution), iOS utilise une extension pour l'implémenter.
L'extension d'intentions
Si vous n'avez pas travaillé avec des extensions, vous devez savoir trois choses principales :
- Une extension est un processus distinct. Il est livré à l'intérieur du bundle de votre application, mais il fonctionne entièrement seul, avec son propre bac à sable.
- Votre application et votre extension peuvent communiquer entre elles en étant dans le même groupe d'applications. Le moyen le plus simple consiste à utiliser les dossiers sandbox partagés du groupe (ainsi, ils peuvent lire et écrire dans les mêmes fichiers si vous les y placez).
- Les extensions nécessitent leurs propres ID d'application, profils et droits.
Pour ajouter une extension à votre application, commencez par vous connecter à votre compte développeur et accédez à la section "Certificats, identifiants et profils".
Mise à jour des données de votre compte Apple Developer App
Dans notre compte de développeur Apple, la première chose que nous devons faire est de créer un groupe d'applications. Accédez à la section "Groupes d'applications" sous "Identifiants" et ajoutez-en un.
Il doit commencer par group
, suivi de votre identifiant de domaine inverse habituel. Comme il a un préfixe, vous pouvez utiliser l'identifiant de votre application pour le reste.
Ensuite, nous devons mettre à jour l'ID de notre application pour utiliser ce groupe et activer Siri :
- Allez dans la section "App IDs" et cliquez sur l'ID de votre application ;
- Cliquez sur le bouton "Modifier" ;
- Activez les groupes d'applications (s'ils ne sont pas activés pour une autre extension).
- Configurez ensuite le groupe d'applications en cliquant sur le bouton "Modifier". Choisissez le groupe d'applications d'avant.
- Activez SiriKit.
- Cliquez sur "Terminé" pour l'enregistrer.
Maintenant, nous devons créer un nouvel ID d'application pour notre extension :
- Dans la même section "ID d'application", ajoutez un nouvel ID d'application. Ce sera l'identifiant de votre application, avec un suffixe. N'utilisez pas uniquement
Intents
comme suffixe car ce nom deviendrait le nom de votre module dans Swift et entrerait alors en conflit avec le vraiIntents
. - Activez également cet ID d'application pour les groupes d'applications (et configurez le groupe comme nous l'avons fait auparavant).
Maintenant, créez un profil d'approvisionnement de développement pour l'extension Intents et régénérez le profil d'approvisionnement de votre application. Téléchargez et installez-les comme vous le feriez normalement.
Maintenant que nos profils sont installés, nous devons accéder à Xcode et mettre à jour les droits de l'application.
Mise à jour des droits de votre application dans Xcode
De retour dans Xcode, choisissez le nom de votre projet dans le navigateur de projet. Ensuite, choisissez la cible principale de votre application et rendez-vous dans l'onglet "Capacités". Là-dedans, vous verrez un interrupteur pour activer le support Siri.
Plus bas dans la liste, vous pouvez activer les groupes d'applications et les configurer.
Si vous l'avez configuré correctement, vous verrez ceci dans le fichier .entitlements
de votre application :
Maintenant, nous sommes enfin prêts à ajouter la cible d'extension Intents à notre projet.
Ajout de l'extension Intents
Nous sommes enfin prêts à ajouter l'extension. Dans Xcode, choisissez "Fichier" → "Nouvelle cible". Cette feuille apparaîtra :
Choisissez "Extension d'intentions" et cliquez sur le bouton "Suivant". Remplissez l'écran suivant :
Le nom du produit doit correspondre à tout ce que vous avez fait comme suffixe dans l'ID d'application d'intention sur le site Web des développeurs Apple.
Nous choisissons de ne pas ajouter d'extension d'interface utilisateur intents. Ce n'est pas couvert dans cet article, mais vous pouvez l'ajouter plus tard si vous en avez besoin. Fondamentalement, c'est un moyen de mettre votre propre image de marque et votre style d'affichage dans les résultats visuels de Siri.
Lorsque vous avez terminé, Xcode créera une classe de gestionnaire d'intentions que nous pourrons utiliser comme élément de départ pour notre implémentation Siri.
Le gestionnaire d'intentions : résoudre, confirmer et gérer
Xcode a généré une nouvelle cible qui a un point de départ pour nous.
La première chose que vous devez faire est de configurer cette nouvelle cible pour qu'elle se trouve dans le même groupe d'applications que l'application. Comme précédemment, accédez à l'onglet "Capacités" de la cible, activez les groupes d'applications et configurez-le avec le nom de votre groupe. N'oubliez pas que les applications du même groupe disposent d'un bac à sable qu'elles peuvent utiliser pour partager des fichiers entre elles. Nous en avons besoin pour que les requêtes Siri parviennent à notre application.
List-o-Mat a une fonction qui renvoie le dossier de documents du groupe. Nous devrions l'utiliser chaque fois que nous voulons lire ou écrire dans un fichier partagé.
func documentsFolder() -> URL? { return FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.app-o-mat.ListOMat") }
Par exemple, lorsque nous sauvegardons les listes, nous utilisons ceci :
func save(lists: Lists) { guard let docsDir = documentsFolder() else { fatalError("no docs dir") } let url = docsDir.appendingPathComponent(fileName, isDirectory: false) // Encode lists as JSON and save to url }
Le modèle d'extension Intents a créé un fichier nommé IntentHandler.swift
, avec une classe nommée IntentHandler
. Il l'a également configuré pour être le point d'entrée des intentions dans le plist
de l'extension.
Dans ce même plist
, vous verrez une section pour déclarer les intents que nous supportons. Nous allons commencer par celui qui permet de rechercher des listes, qui s'appelle INSearchForNotebookItemsIntent
. Ajoutez-le au tableau sous IntentsSupported
.
Maintenant, allez dans IntentHandler.swift
et remplacez son contenu par ce code :
import Intents class IntentHandler: INExtension { override func handler(for intent: INIntent) -> Any? { switch intent { case is INSearchForNotebookItemsIntent: return SearchItemsIntentHandler() default: return nil } } }
La fonction de handler
est appelée pour qu'un objet gère une intention spécifique. Vous pouvez simplement implémenter tous les protocoles de cette classe et renvoyer self
, mais nous placerons chaque intent dans sa propre classe pour mieux l'organiser.
Parce que nous avons l'intention d'avoir quelques classes différentes, donnons-leur une classe de base commune pour le code que nous devons partager entre elles :
class ListOMatIntentsHandler: NSObject { }
Le framework d'intentions nous oblige à hériter de NSObject
. Nous détaillerons quelques méthodes plus tard.
Nous commençons notre implémentation de recherche avec ceci :
class SearchItemsIntentHandler: ListOMatIntentsHandler, INSearchForNotebookItemsIntentHandling { }
Pour définir un gestionnaire d'intention, nous devons implémenter trois étapes de base
- Résolvez les paramètres.
Assurez-vous que les paramètres requis sont donnés et désambiguïsez ceux que vous ne comprenez pas entièrement. - Confirmez que la demande est réalisable.
Ceci est souvent facultatif, mais même si vous savez que chaque paramètre est bon, vous devrez peut-être toujours accéder à une ressource externe ou avoir d'autres exigences. - Traiter la demande.
Faites la chose qui est demandée.
INSearchForNotebookItemsIntent
, la première intention que nous allons implémenter, peut être utilisée comme recherche de tâches. Les types de demandes que nous pouvons traiter avec cela sont "Dans List-o-Mat, affichez la liste des épiceries" ou "Dans List-o-Mat, affichez la liste des magasins".
A part: "List-o-Mat" est en fait un mauvais nom pour une application SiriKit car Siri a du mal avec les traits d'union dans les applications. Heureusement, SiriKit nous permet d'avoir des noms alternatifs et de fournir une prononciation. Dans le Info.plist
de l'application, ajoutez cette section :
Cela permet à l'utilisateur de dire "list oh mat" et que cela soit compris comme un seul mot (sans tirets). Cela n'a pas l'air idéal sur l'écran, mais sans cela, Siri pense parfois que "Liste" et "Mat" sont des mots séparés et devient très confus.
Résoudre : déterminer les paramètres
Pour une recherche d'éléments de carnet, il existe plusieurs paramètres :
- le type d'élément (une tâche, une liste de tâches ou une note),
- le titre de l'article,
- le contenu de l'article,
- le statut d'achèvement (que la tâche soit marquée comme terminée ou non),
- le lieu auquel il est associé,
- la date à laquelle il est associé.
Nous n'avons besoin que des deux premiers, nous devrons donc écrire des fonctions de résolution pour eux. INSearchForNotebookItemsIntent
a des méthodes à implémenter.
Comme nous nous soucions uniquement d'afficher les listes de tâches, nous les coderons en dur dans la résolution du type d'élément. Dans SearchItemsIntentHandler
, ajoutez ceci :
func resolveItemType(for intent: INSearchForNotebookItemsIntent, with completion: @escaping (INNotebookItemTypeResolutionResult) -> Void) { completion(.success(with: .taskList)) }
Ainsi, peu importe ce que dit l'utilisateur, nous rechercherons des listes de tâches. Si nous voulions étendre notre prise en charge de la recherche, nous laisserions Siri essayer de comprendre cela à partir de la phrase d'origine, puis utiliserions simplement l' completion(.needsValue())
si le type d'élément était manquant. Alternativement, nous pourrions essayer de deviner à partir du titre en voyant ce qui lui correspond. Dans ce cas, nous compléterions avec succès lorsque Siri sait ce que c'est, et nous utiliserions l' completion(.notRequired())
lorsque nous allons essayer plusieurs possibilités.
La résolution des titres est un peu plus délicate. Ce que nous voulons, c'est que Siri utilise une liste s'il en trouve une avec une correspondance exacte pour ce que vous avez dit. Si ce n'est pas sûr ou s'il y a plus d'une possibilité, nous voulons que Siri nous demande de l'aide pour le découvrir. Pour ce faire, SiriKit fournit un ensemble d'énumérations de résolution qui nous permettent d'exprimer ce que nous voulons qu'il se passe ensuite.
Donc, si vous dites "Épicerie", Siri aurait une correspondance exacte. Mais si vous dites "Store", Siri présentera un menu de listes correspondantes.
Nous allons commencer par cette fonction pour donner la structure de base :
func resolveTitle(for intent: INSearchForNotebookItemsIntent, with completion: @escaping (INSpeakableStringResolutionResult) -> Void) { guard let title = intent.title else { completion(.needsValue()) return } let possibleLists = getPossibleLists(for: title) completeResolveListName(with: possibleLists, for: title, with: completion) }
Nous allons implémenter getPossibleLists(for:)
et completeResolveListName(with:for:with:)
dans la classe de base ListOMatIntentsHandler
.
getPossibleLists(for:)
doit essayer de faire correspondre le titre que Siri nous transmet avec les noms de liste réels.
public func getPossibleLists(for listName: INSpeakableString) -> [INSpeakableString] { var possibleLists = [INSpeakableString]() for l in loadLists() { if l.name.lowercased() == listName.spokenPhrase.lowercased() { return [INSpeakableString(spokenPhrase: l.name)] } if l.name.lowercased().contains(listName.spokenPhrase.lowercased()) || listName.spokenPhrase.lowercased() == "all" { possibleLists.append(INSpeakableString(spokenPhrase: l.name)) } } return possibleLists }
Nous parcourons toutes nos listes. Si nous obtenons une correspondance exacte, nous la renverrons, et sinon, nous renverrons un tableau de possibilités. Dans cette fonction, nous vérifions simplement si le mot prononcé par l'utilisateur est contenu dans un nom de liste (donc, une correspondance assez simple). Cela permet à "Épicerie" de correspondre à "Épicerie". Un algorithme plus avancé pourrait essayer de faire correspondre des mots qui se prononcent de la même manière (par exemple, avec l'algorithme Soundex),
completeResolveListName(with:for:with:)
est chargé de décider quoi faire avec cette liste de possibilités.
public func completeResolveListName(with possibleLists: [INSpeakableString], for listName: INSpeakableString, with completion: @escaping (INSpeakableStringResolutionResult) -> Void) { switch possibleLists.count { case 0: completion(.unsupported()) case 1: if possibleLists[0].spokenPhrase.lowercased() == listName.spokenPhrase.lowercased() { completion(.success(with: possibleLists[0])) } else { completion(.confirmationRequired(with: possibleLists[0])) } default: completion(.disambiguation(with: possibleLists)) } }
Si nous avons une correspondance exacte, nous disons à Siri que nous avons réussi. Si nous avons une correspondance inexacte, nous disons à Siri de demander à l'utilisateur si nous l'avons bien deviné.
Si nous obtenons plusieurs correspondances, nous utilisons la completion(.disambiguation(with: possibleLists))
pour dire à Siri d'afficher une liste et de laisser l'utilisateur en choisir une.
Maintenant que nous savons quelle est la demande, nous devons examiner l'ensemble et nous assurer que nous pouvons la gérer.
Confirmer : vérifier toutes vos dépendances
Dans ce cas, si nous avons résolu tous les paramètres, nous pouvons toujours traiter la requête. Les implémentations typiques de confirm()
peuvent vérifier la disponibilité de services externes ou vérifier les niveaux d'autorisation.
Étant donné que confirm()
est facultatif, nous ne pourrions rien faire, et Siri supposerait que nous pourrions gérer n'importe quelle demande avec des paramètres résolus. Pour être explicite, nous pourrions utiliser ceci :
func confirm(intent: INSearchForNotebookItemsIntent, completion: @escaping (INSearchForNotebookItemsIntentResponse) -> Void) { completion(INSearchForNotebookItemsIntentResponse(code: .success, userActivity: nil)) }
Cela signifie que nous pouvons tout gérer.
Poignée : Faites-le
La dernière étape consiste à traiter la demande.
func handle(intent: INSearchForNotebookItemsIntent, completion: @escaping (INSearchForNotebookItemsIntentResponse) -> Void) { guard let title = intent.title, let list = loadLists().filter({ $0.name.lowercased() == title.spokenPhrase.lowercased()}).first else { completion(INSearchForNotebookItemsIntentResponse(code: .failure, userActivity: nil)) return } let response = INSearchForNotebookItemsIntentResponse(code: .success, userActivity: nil) response.tasks = list.items.map { return INTask(title: INSpeakableString(spokenPhrase: $0.name), status: $0.done ? INTaskStatus.completed : INTaskStatus.notCompleted, taskType: INTaskType.notCompletable, spatialEventTrigger: nil, temporalEventTrigger: nil, createdDateComponents: nil, modifiedDateComponents: nil, identifier: "\(list.name)\t\($0.name)") } completion(response) }
Tout d'abord, nous trouvons la liste basée sur le titre. À ce stade, resolveTitle
s'est déjà assuré que nous obtiendrons une correspondance exacte. Mais s'il y a un problème, nous pouvons toujours renvoyer un échec.
Lorsque nous avons un échec, nous avons la possibilité de transmettre une activité utilisateur. Si votre application utilise Handoff et dispose d'un moyen de gérer ce type exact de demande, Siri peut essayer de s'en remettre à votre application pour y essayer la demande. Il ne le fera pas lorsque nous sommes dans un contexte uniquement vocal (par exemple, vous avez commencé avec "Hey Siri"), et cela ne garantit pas qu'il le fera dans d'autres cas, alors ne comptez pas dessus.
Ceci est maintenant prêt à tester. Choisissez l'extension d'intention dans la liste cible dans Xcode. Mais avant de l'exécuter, modifiez le schéma.
Cela amène un moyen de fournir une requête directement :
Remarquez que j'utilise "ListOMat" à cause du problème de tirets mentionné ci-dessus. Heureusement, il se prononce de la même manière que le nom de mon application, donc cela ne devrait pas poser de problème.
De retour dans l'application, j'ai créé une liste "Épicerie" et une liste "Magasin de quincaillerie". Si je demande à Siri la liste des « magasins », il passera par le chemin de désambiguïsation, qui ressemble à ceci :
Si vous dites «Épicerie», vous obtiendrez une correspondance exacte, qui ira directement aux résultats.
Ajouter des éléments via Siri
Maintenant que nous connaissons les concepts de base de la résolution, de la confirmation et de la gestion, nous pouvons rapidement ajouter une intention pour ajouter un élément à une liste.
Tout d'abord, ajoutez INAddTasksIntent
au plist de l'extension :
Ensuite, mettez à jour la fonction handle
de notre IntentHandler
.
override func handler(for intent: INIntent) -> Any? { switch intent { case is INSearchForNotebookItemsIntent: return SearchItemsIntentHandler() case is INAddTasksIntent: return AddItemsIntentHandler() default: return nil } }
Ajoutez un stub pour la nouvelle classe :
class AddItemsIntentHandler: ListOMatIntentsHandler, INAddTasksIntentHandling { }
L'ajout d'un élément nécessite une resolve
similaire pour la recherche, sauf avec une liste de tâches cible au lieu d'un titre.
func resolveTargetTaskList(for intent: INAddTasksIntent, with completion: @escaping (INTaskListResolutionResult) -> Void) { guard let title = intent.targetTaskList?.title else { completion(.needsValue()) return } let possibleLists = getPossibleLists(for: title) completeResolveTaskList(with: possibleLists, for: title, with: completion) }
completeResolveTaskList
est comme completeResolveListName
, mais avec des types légèrement différents (une liste de tâches au lieu du titre d'une liste de tâches).
public func completeResolveTaskList(with possibleLists: [INSpeakableString], for listName: INSpeakableString, with completion: @escaping (INTaskListResolutionResult) -> Void) { let taskLists = possibleLists.map { return INTaskList(title: $0, tasks: [], groupName: nil, createdDateComponents: nil, modifiedDateComponents: nil, identifier: nil) } switch possibleLists.count { case 0: completion(.unsupported()) case 1: if possibleLists[0].spokenPhrase.lowercased() == listName.spokenPhrase.lowercased() { completion(.success(with: taskLists[0])) } else { completion(.confirmationRequired(with: taskLists[0])) } default: completion(.disambiguation(with: taskLists)) } }
Il a la même logique de désambiguïsation et se comporte exactement de la même manière. Dire "Magasin" doit être désambiguïsé, et dire "Épicerie" serait une correspondance exacte.
Nous laisserons la confirm
non implémentée et accepterons la valeur par défaut. Pour handle
, nous devons ajouter un élément à la liste et l'enregistrer.
func handle(intent: INAddTasksIntent, completion: @escaping (INAddTasksIntentResponse) -> Void) { var lists = loadLists() guard let taskList = intent.targetTaskList, let listIndex = lists.index(where: { $0.name.lowercased() == taskList.title.spokenPhrase.lowercased() }), let itemNames = intent.taskTitles, itemNames.count > 0 else { completion(INAddTasksIntentResponse(code: .failure, userActivity: nil)) return } // Get the list var list = lists[listIndex] // Add the items var addedTasks = [INTask]() for item in itemNames { list.addItem(name: item.spokenPhrase, at: list.items.count) addedTasks.append(INTask(title: item, status: .notCompleted, taskType: .notCompletable, spatialEventTrigger: nil, temporalEventTrigger: nil, createdDateComponents: nil, modifiedDateComponents: nil, identifier: nil)) } // Save the new list lists[listIndex] = list save(lists: lists) // Respond with the added items let response = INAddTasksIntentResponse(code: .success, userActivity: nil) response.addedTasks = addedTasks completion(response) }
Nous obtenons une liste d'éléments et une liste cible. Nous consultons la liste et ajoutons les éléments. Nous devons également préparer une réponse pour Siri à afficher avec les éléments ajoutés et l'envoyer à la fonction d'achèvement.
Cette fonction peut gérer une phrase telle que "Dans ListOMat, ajoutez des pommes à la liste d'épicerie". Il peut également gérer une liste d'articles tels que "riz, oignons et olives".
Presque terminé, juste quelques paramètres supplémentaires
Tout cela fonctionnera dans votre simulateur ou votre appareil local, mais si vous souhaitez le soumettre, vous devrez ajouter une clé NSSiriUsageDescription
au plist
de votre application, avec une chaîne décrivant la raison pour laquelle vous utilisez Siri. Quelque chose comme "Vos demandes concernant les listes seront envoyées à Siri" est très bien.
Vous devez également ajouter un appel à :
INPreferences.requestSiriAuthorization { (status) in }
Mettez ceci dans le viewDidLoad
de votre contrôleur de vue principal pour demander à l'utilisateur l'accès à Siri. This will show the message you configured above and also let the user know that they could be using Siri for this app.
Finally, you'll need to tell Siri what to tell the user if the user asks what your app can do, by providing some sample phrases:
- Create a
plist
file in your app (not the extension), namedAppIntentVocabulary.plist
. - Fill out the intents and phrases that you support.
There is no way to really know all of the phrases that Siri will use for an intent, but Apple does provide a few samples for each intent in its documentation. The sample phrases for task-list searching show us that Siri can understand “Show me all my notes on <appName>,” but I found other phrases by trial and error (for example, Siri understands what “lists” are too, not just notes).
Sommaire
As you can see, adding Siri support to an app has a lot of steps, with a lot of configuration. But the code needed to handle the requests was fairly simple.
There are a lot of steps, but each one is small, and you might be familiar with a few of them if you have used extensions before.
Here is what you'll need to prepare for a new extension on Apple's developer website:
- Make an app ID for an Intents extension.
- Make an app group if you don't already have one.
- Use the app group in the app ID for the app and extension.
- Add Siri support to the app's ID.
- Regenerate the profiles and download them.
And here are the steps in Xcode for creating Siri's Intents extension:
- Add an Intents extension using the Xcode template.
- Update the entitlements of the app and extension to match the profiles (groups and Siri support).
- Add your intents to the extension's
plist
.
And you'll need to add code to do the following things:
- Use the app group sandbox to communicate between the app and extension.
- Add classes to support each intent with resolve, confirm and handle functions.
- Update the generated
IntentHandler
to use those classes. - Ask for Siri access somewhere in your app.
Finally, there are some Siri-specific configuration settings:
- Add the Siri support security string to your app's
plist
. - Add sample phrases to an
AppIntentVocabulary.plist
file in your app. - Run the intent target to test; edit the scheme to provide the phrase.
OK, that is a lot, but if your app fits one of Siri's domains, then users will expect that they can interact with it via voice. And because the competition for voice assistants is so good, we can only expect that WWDC 2018 will bring a bunch more domains and, hopefully, much better Siri.
Lectures complémentaires
- “SiriKit,” Apple
The technical documentation contains the full list of domains and intents. - “Guides and Sample Code,” Apple
Includes code for many domains. - “Introducing SiriKit” (video, Safari only), WWDC 2016 Apple
- “What's New in SiriKit” (video, Safari only), WWDC 2017, Apple
Apple introduces lists and notes - “Lists and Notes,” Apple
The full list of lists and notes intents.