Gli intenti di SiriKit si adatteranno alla tua app? Se è così, ecco come usarli
Pubblicato: 2022-03-10Da iOS 5, Siri ha aiutato gli utenti iPhone a inviare messaggi, impostare promemoria e cercare ristoranti con le app di Apple. A partire da iOS 10, siamo stati in grado di utilizzare Siri anche in alcune delle nostre app.
Per utilizzare questa funzionalità, la tua app deve rientrare nei "domini e intenti" Siri predefiniti di Apple. In questo articolo impareremo cosa sono e vedremo se le nostre app possono usarle. Prenderemo una semplice app che è un gestore di elenchi di cose da fare e impareremo come aggiungere il supporto di Siri. Esamineremo anche le linee guida del sito Web degli sviluppatori Apple sulla configurazione e il codice Swift per un nuovo tipo di estensione introdotto con SiriKit: l'estensione Intents .
Quando arrivi alla parte di codifica di questo articolo, avrai bisogno di Xcode (almeno la versione 9.x) e sarebbe utile se hai familiarità con lo sviluppo di iOS in Swift perché aggiungeremo Siri a un piccolo lavoro app. Esamineremo i passaggi per configurare un'estensione sul sito Web degli sviluppatori Apple e aggiungere il codice dell'estensione Siri all'app.
"Ehi Siri, perché ho bisogno di te?"
A volte uso il telefono mentre sono sul divano, con entrambe le mani libere, e posso dedicare tutta la mia attenzione allo schermo. Magari manderò un messaggio a mia sorella per organizzare il compleanno di nostra madre o risponderò a una domanda su Trello. Posso vedere l'app. Posso toccare lo schermo. Posso scrivere.
Ma potrei essere in giro per la mia città, ascoltando un podcast, quando un messaggio arriva sul mio orologio. Ho il telefono in tasca e non riesco a rispondere facilmente mentre cammino.
Con Siri, posso tenere premuto il pulsante di controllo delle mie cuffie e dire: "Invia un SMS a mia sorella che sarò lì per le due". Siri è fantastico quando sei in movimento e non puoi prestare la massima attenzione al tuo telefono o quando l'interazione è minore, ma richiede diversi tocchi e un po' di digitazione.
Questo va bene se voglio usare le app Apple per queste interazioni. Ma alcune categorie di app, come la messaggistica, hanno alternative molto popolari. Altre attività, come prenotare un passaggio o prenotare un tavolo in un ristorante, non sono nemmeno possibili con le app integrate di Apple, ma sono perfette per Siri.
Approccio di Apple agli assistenti vocali
Per abilitare Siri nelle app di terze parti, Apple ha dovuto decidere un meccanismo per prendere il suono dalla voce dell'utente e in qualche modo portarlo all'app in modo da poter soddisfare la richiesta. Per renderlo possibile, Apple richiede all'utente di menzionare il nome dell'app nella richiesta, ma avevano diverse opzioni su cosa fare con il resto della richiesta.
- Potrebbe aver inviato un file audio all'app.
Il vantaggio di questo approccio è che l'app potrebbe provare a gestire letteralmente qualsiasi richiesta che l'utente potrebbe avere per essa. Amazon o Google potrebbero aver apprezzato questo approccio perché dispongono già di sofisticati servizi di riconoscimento vocale. Ma la maggior parte delle app non sarebbe in grado di gestirlo molto facilmente. - Avrebbe potuto trasformare il discorso in testo e inviarlo.
Poiché molte app non dispongono di sofisticate implementazioni in linguaggio naturale, l'utente dovrebbe in genere attenersi a frasi molto particolari e il supporto non in inglese spetterebbe allo sviluppatore dell'app da implementare. - Potrebbe averti chiesto di fornire un elenco di frasi che capisci.
Questo meccanismo è più vicino a ciò che Amazon fa con Alexa (nel suo framework "skills") e consente molti più usi di Alexa di quanti SiriKit possa attualmente gestire. In una Skill Alexa, fornisci frasi con variabili segnaposto che Alexa compilerà per te. Ad esempio, "Alexa, ricordami da$TIME$
a$REMINDER$
" — Alexa eseguirà questa frase rispetto a ciò che l'utente ha detto e ti dirà i valori perTIME
eREMINDER
. Come con il meccanismo precedente, lo sviluppatore deve eseguire tutta la traduzione e non c'è molta flessibilità se l'utente dice qualcosa di leggermente diverso. - Potrebbe definire un elenco di richieste con parametri e inviare all'app una richiesta strutturata.
Questo è in realtà ciò che fa Apple e il vantaggio è che può supportare una varietà di lingue e fa tutto il lavoro per cercare di capire tutti i modi in cui un utente potrebbe formulare una richiesta. Il grande svantaggio è che puoi implementare solo gestori per le richieste che Apple definisce. Questo è fantastico se hai, ad esempio, un'app di messaggistica, ma se hai un servizio di streaming musicale o un lettore di podcast, non hai modo di usare SiriKit in questo momento.
Allo stesso modo, ci sono tre modi in cui le app rispondono all'utente: con il suono, con il testo che viene convertito o esprimendo il tipo di cosa che vuoi dire e lasciando che il sistema capisca il modo esatto per esprimerla. L'ultima soluzione (che è ciò che fa Apple) mette l'onere della traduzione su Apple, ma ti offre modi limitati per usare le tue stesse parole per descrivere le cose.
I tipi di richieste che puoi gestire sono definiti nei domini e negli intenti di SiriKit. Un intento è un tipo di richiesta che un utente potrebbe fare, come inviare un SMS a un contatto o trovare una foto. Ogni intento ha un elenco di parametri, ad esempio, l'invio di messaggi di testo richiede un contatto e un messaggio.
Un dominio è solo un gruppo di intenti correlati. La lettura di un testo e l'invio di un testo sono entrambi nel dominio della messaggistica. Prenotare una corsa e ottenere una posizione sono nel dominio di prenotazione della corsa. Ci sono domini per effettuare chiamate VoIP, iniziare allenamenti, cercare foto e altre cose. La documentazione di SiriKit contiene un elenco completo dei domini e dei loro intenti.
Una critica comune a Siri è che sembra incapace di gestire le richieste così come Google e Alexa, e che l'ecosistema vocale di terze parti abilitato dai concorrenti di Apple è più ricco.
Sono d'accordo con queste critiche. Se la tua app non rientra nelle intenzioni attuali, non puoi utilizzare SiriKit e non puoi fare nulla. Anche se la tua app si adatta, non puoi controllare tutte le parole che Siri dice o comprende; quindi, se hai un modo particolare di parlare delle cose nella tua app, non puoi sempre insegnarlo a Siri.
La speranza degli sviluppatori iOS è sia che Apple allarghi notevolmente il suo elenco di intenti sia che l'elaborazione del linguaggio naturale diventi molto migliore. In tal caso, avremo un assistente vocale che funziona senza che gli sviluppatori debbano eseguire traduzioni o comprendere tutti i modi per dire la stessa cosa. E implementare il supporto per le richieste strutturate è in realtà abbastanza semplice da fare, molto più semplice della creazione di un parser in linguaggio naturale.
Un altro grande vantaggio del framework degli intenti è che non si limita a Siri e alle richieste vocali. Anche ora, l'app Maps può generare una richiesta basata sugli intenti della tua app (ad esempio, una prenotazione di un ristorante). Lo fa in modo programmatico (non dalla voce o dal linguaggio naturale). Se Apple consentisse alle app di scoprire le reciproche intenzioni esposte, avremmo un modo molto migliore per far funzionare insieme le app (al contrario degli URL in stile x-callback).
Infine, poiché un intento è una richiesta strutturata con parametri, esiste un modo semplice per un'app per esprimere che mancano parametri o che ha bisogno di aiuto per distinguere tra alcune opzioni. Siri può quindi porre domande di follow-up per risolvere i parametri senza che l'app debba condurre la conversazione.
Il dominio della prenotazione di corse
Per comprendere domini e intenti, diamo un'occhiata al dominio di prenotazione delle corse. Questo è il dominio che useresti per chiedere a Siri di procurarti un'auto Lyft.
Apple definisce come chiedere un passaggio e come ottenere informazioni al riguardo, ma in realtà non esiste un'app Apple integrata in grado di gestire questa richiesta. Questo è uno dei pochi domini in cui è richiesta un'app abilitata per SiriKit.
Puoi invocare uno degli intenti tramite voce o direttamente da Maps. Alcuni degli intenti per questo dominio sono:
- Richiedi un passaggio
Usa questo per prenotare una corsa. Dovrai fornire un luogo di ritiro e riconsegna e l'app potrebbe anche aver bisogno di conoscere le dimensioni della tua festa e che tipo di corsa desideri. Una frase di esempio potrebbe essere "Prenotami un passaggio con <appname>". - Ottieni lo stato della corsa
Usa questo intento per scoprire se la tua richiesta è stata ricevuta e per ottenere informazioni sul veicolo e sul conducente, inclusa la loro posizione. L'app Mappe utilizza questo intento per mostrare un'immagine aggiornata dell'auto mentre si avvicina a te. - Annulla una corsa
Usalo per annullare una corsa che hai prenotato.
A tal fine, Siri potrebbe aver bisogno di ulteriori informazioni. Come vedrai quando implementiamo un gestore di intenti, la tua estensione Intents può dire a Siri che manca un parametro richiesto e Siri lo chiederà all'utente.
Il fatto che gli intenti possano essere invocati a livello di codice da Maps mostra come gli intenti potrebbero consentire la comunicazione tra le app in futuro.
Nota : puoi ottenere un elenco completo dei domini e dei loro intenti sul sito Web degli sviluppatori di Apple. C'è anche un'app Apple di esempio con molti domini e intenti implementati, inclusa la prenotazione di corse.
Aggiunta di elenchi e note Supporto del dominio alla tua app
OK, ora che comprendiamo le basi di SiriKit, diamo un'occhiata a come aggiungeresti il supporto per Siri in un'app che richiede molta configurazione e una classe per ogni intento che desideri gestire.
Il resto di questo articolo è costituito dai passaggi dettagliati per aggiungere il supporto Siri a un'app. Ci sono cinque cose di alto livello che devi fare:
- Preparati ad aggiungere una nuova estensione all'app creando profili di provisioning con nuovi diritti per essa sul sito Web degli sviluppatori di Apple.
- Configura la tua app (tramite il suo
plist
) per utilizzare i diritti. - Usa il modello di Xcode per iniziare con del codice di esempio.
- Aggiungi il codice per supportare il tuo intento Siri.
- Configura il vocabolario di Siri tramite
plist
s.
Non preoccuparti: esamineremo ciascuno di questi, spiegando estensioni e diritti lungo il percorso.
Per concentrarmi solo sulle parti di Siri, ho preparato un semplice gestore di elenchi di cose da fare, List-o-Mat.
Puoi trovare la fonte completa dell'esempio, List-o-Mat, su GitHub.
Per crearlo, tutto ciò che ho fatto è stato iniziare con il modello di app Xcode Master-Detail e trasformare entrambe le schermate in un UITableView
. Ho aggiunto un modo per aggiungere ed eliminare elenchi ed elementi e un modo per contrassegnare gli elementi come fatto. Tutta la navigazione è generata dal modello.
Per archiviare i dati, ho utilizzato il protocollo Codable
, (introdotto al WWDC 2017), che trasforma le strutture in JSON e le salva in un file di testo nella cartella dei documents
.
Ho volutamente mantenuto il codice molto semplice. Se hai esperienza con Swift e la creazione di controller di visualizzazione, non dovresti avere problemi con esso.
Ora possiamo seguire i passaggi per aggiungere il supporto SiriKit. I passaggi di alto livello sarebbero gli stessi per qualsiasi app e qualsiasi dominio e intento che intendi implementare. Ci occuperemo principalmente del sito Web degli sviluppatori Apple, della modifica di plist
e della scrittura di un po' di Swift.
Per List-o-Mat, ci concentreremo sul dominio degli elenchi e delle note, che è ampiamente applicabile a cose come le app per prendere appunti e gli elenchi di cose da fare.
Nel dominio elenchi e note, abbiamo i seguenti intenti che avrebbero senso per la nostra app.
- Ottieni un elenco di attività.
- Aggiungi una nuova attività a un elenco.
Poiché le interazioni con Siri si verificano effettivamente al di fuori della tua app (forse anche quando l'app non è in esecuzione), iOS utilizza un'estensione per implementarla.
L'estensione degli intenti
Se non hai lavorato con le estensioni, devi sapere tre cose principali:
- Un'estensione è un processo separato. Viene fornito all'interno del bundle della tua app, ma funziona completamente da solo, con la propria sandbox.
- L'app e l'interno possono comunicare tra loro trovandosi nello stesso gruppo di app. Il modo più semplice è tramite le cartelle sandbox condivise del gruppo (quindi possono leggere e scrivere sugli stessi file se li metti lì).
- Le estensioni richiedono i propri ID app, profili e diritti.
Per aggiungere un'estensione alla tua app, inizia accedendo al tuo account sviluppatore e andando alla sezione "Certificati, identificatori e profili".
Aggiornamento dei dati dell'account dell'app per sviluppatori Apple
Nel nostro account sviluppatore Apple, la prima cosa che dobbiamo fare è creare un gruppo di app. Vai alla sezione "Gruppi di app" in "Identificatori" e aggiungine uno.
Deve iniziare con group
, seguito dal solito identificatore basato sul dominio inverso. Poiché ha un prefisso, puoi utilizzare l'identificatore della tua app per il resto.
Quindi, dobbiamo aggiornare l'ID della nostra app per utilizzare questo gruppo e abilitare Siri:
- Vai alla sezione "ID app" e fai clic sull'ID della tua app;
- Fare clic sul pulsante "Modifica";
- Abilita gruppi di app (se non abilitato per un altro interno).
- Quindi configura il gruppo di app facendo clic sul pulsante "Modifica". Scegli il gruppo di app di prima.
- Abilita SiriKit.
- Fare clic su "Fatto" per salvarlo.
Ora dobbiamo creare un nuovo ID app per la nostra estensione:
- Nella stessa sezione "ID app", aggiungi un nuovo ID app. Questo sarà l'identificatore della tua app, con un suffisso. Non utilizzare solo
Intents
come suffisso perché questo nome diventerà il nome del tuo modulo in Swift e sarebbe quindi in conflitto con gliIntents
reali. - Abilita questo ID app anche per i gruppi di app (e configura il gruppo come abbiamo fatto prima).
Ora crea un profilo di provisioning di sviluppo per l'estensione Intents e rigenera il profilo di provisioning della tua app. Scaricali e installali come faresti normalmente.
Ora che i nostri profili sono installati, dobbiamo andare su Xcode e aggiornare i diritti dell'app.
Aggiornare i diritti della tua app in Xcode
Di nuovo in Xcode, scegli il nome del tuo progetto nel navigatore del progetto. Quindi, scegli la destinazione principale della tua app e vai alla scheda "Capacità". Lì vedrai un interruttore per attivare il supporto di Siri.
Più in basso nell'elenco, puoi attivare i gruppi di app e configurarlo.
Se l'hai configurato correttamente, vedrai questo nel file .entitlements
della tua app:
Ora siamo finalmente pronti per aggiungere il target dell'estensione Intents al nostro progetto.
Aggiunta dell'estensione di intenti
Siamo finalmente pronti per aggiungere l'estensione. In Xcode, scegli "File" → "Nuovo obiettivo". Apparirà questo foglio:
Scegli "Estensione intenti" e fai clic sul pulsante "Avanti". Compila la seguente schermata:
Il nome del prodotto deve corrispondere a qualsiasi cosa tu abbia creato il suffisso nell'ID app intents sul sito Web dello sviluppatore Apple.
Stiamo scegliendo di non aggiungere un'estensione dell'interfaccia utente di intents. Questo non è trattato in questo articolo, ma puoi aggiungerlo in seguito se ne hai bisogno. Fondamentalmente, è un modo per inserire il tuo marchio e il tuo stile di visualizzazione nei risultati visivi di Siri.
Al termine, Xcode creerà una classe di gestori di intenti che possiamo utilizzare come parte iniziale per la nostra implementazione di Siri.
Il gestore degli intenti: risolvere, confermare e gestire
Xcode ha generato un nuovo target che ha un punto di partenza per noi.
La prima cosa che devi fare è impostare questo nuovo target in modo che si trovi nello stesso gruppo di app dell'app. Come prima, vai alla scheda "Capacità" del target, attiva i gruppi di app e configuralo con il nome del tuo gruppo. Ricorda, le app nello stesso gruppo hanno una sandbox che possono utilizzare per condividere file tra loro. Ne abbiamo bisogno affinché le richieste di Siri raggiungano la nostra app.
List-o-Mat ha una funzione che restituisce la cartella dei documenti di gruppo. Dovremmo usarlo ogni volta che vogliamo leggere o scrivere su un file condiviso.
func documentsFolder() -> URL? { return FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.app-o-mat.ListOMat") }
Ad esempio, quando salviamo gli elenchi, utilizziamo questo:
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 }
Il modello di estensione Intents ha creato un file denominato IntentHandler.swift
, con una classe denominata IntentHandler
. Lo ha anche configurato come punto di ingresso degli intenti nel plist
dell'estensione.
In questo stesso plist
, vedrai una sezione per dichiarare gli intenti che supportiamo. Inizieremo con quello che consente la ricerca di elenchi, denominato INSearchForNotebookItemsIntent
. Aggiungilo all'array in IntentsSupported
.
Ora vai su IntentHandler.swift
e sostituisci il suo contenuto con questo codice:
import Intents class IntentHandler: INExtension { override func handler(for intent: INIntent) -> Any? { switch intent { case is INSearchForNotebookItemsIntent: return SearchItemsIntentHandler() default: return nil } } }
La funzione del handler
viene chiamata per ottenere un oggetto per gestire un intento specifico. Puoi semplicemente implementare tutti i protocolli in questa classe e restituire self
, ma metteremo ogni intento nella sua classe per mantenerlo meglio organizzato.
Poiché intendiamo avere alcune classi diverse, diamo loro una classe base comune per il codice che dobbiamo condividere tra loro:
class ListOMatIntentsHandler: NSObject { }
Il framework intents ci richiede di ereditare da NSObject
. Compileremo alcuni metodi in seguito.
Iniziamo la nostra implementazione della ricerca con questo:
class SearchItemsIntentHandler: ListOMatIntentsHandler, INSearchForNotebookItemsIntentHandling { }
Per impostare un gestore di intenti, dobbiamo implementare tre passaggi di base
- Risolvi i parametri.
Assicurati che i parametri richiesti siano forniti e disambigua quelli che non comprendi completamente. - Conferma che la richiesta è fattibile.
Questo è spesso facoltativo, ma anche se sai che ogni parametro è buono, potresti comunque aver bisogno di accedere a una risorsa esterna o avere altri requisiti. - Gestisci la richiesta.
Fai la cosa che ti viene richiesta.
INSearchForNotebookItemsIntent
, il primo intento che implementeremo, può essere utilizzato come ricerca di attività. I tipi di richieste che possiamo gestire con questo sono "In List-o-Mat, mostra l'elenco dei negozi di alimentari" o "In List-o-Mat, mostra l'elenco dei negozi".
A parte: "List-o-Mat" è in realtà un brutto nome per un'app SiriKit perché Siri ha difficoltà con i trattini nelle app. Fortunatamente, SiriKit ci consente di avere nomi alternativi e di fornire la pronuncia. In Info.plist
, aggiungi questa sezione:
Ciò consente all'utente di dire "list oh mat" e di intenderlo come una singola parola (senza trattini). Non sembra l'ideale sullo schermo, ma senza di esso, a volte Siri pensa che "Lista" e "Tappetino" siano parole separate e si confonde molto.
Risolvere: capire i parametri
Per una ricerca di elementi del taccuino, sono disponibili diversi parametri:
- il tipo di elemento (un'attività, un elenco di attività o una nota),
- il titolo dell'oggetto,
- il contenuto dell'oggetto,
- lo stato di completamento (se l'attività è contrassegnata come completata o meno),
- il luogo a cui è associato,
- la data a cui è associato.
Abbiamo bisogno solo dei primi due, quindi dovremo scrivere funzioni di risoluzione per loro. INSearchForNotebookItemsIntent
ha metodi da implementare.
Poiché ci interessa solo mostrare gli elenchi di attività, lo inseriremo nella risoluzione per il tipo di elemento. In SearchItemsIntentHandler
, aggiungi questo:
func resolveItemType(for intent: INSearchForNotebookItemsIntent, with completion: @escaping (INNotebookItemTypeResolutionResult) -> Void) { completion(.success(with: .taskList)) }
Quindi, indipendentemente da ciò che dice l'utente, cercheremo elenchi di attività. Se volessimo espandere il nostro supporto per la ricerca, consentiremmo a Siri di provare a capirlo dalla frase originale e quindi utilizzare completion(.needsValue())
se il tipo di elemento mancava. In alternativa, potremmo provare a indovinare dal titolo vedendo cosa corrisponde. In questo caso, completeremmo con successo quando Siri sa di cosa si tratta e completion(.notRequired())
quando proveremo più possibilità.
La risoluzione del titolo è un po' più complicata. Quello che vogliamo è che Siri utilizzi un elenco se ne trova uno con una corrispondenza esatta per ciò che hai detto. Se non è sicuro o se c'è più di una possibilità, allora vogliamo che Siri ci chieda aiuto per capirlo. Per fare ciò, SiriKit fornisce una serie di enumerazioni di risoluzione che ci consentono di esprimere ciò che vogliamo che accada dopo.
Quindi, se dici "Negozio di alimentari", Siri avrebbe una corrispondenza esatta. Ma se dici "Store", Siri presenterebbe un menu di elenchi corrispondenti.
Inizieremo con questa funzione per dare la struttura di 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) }
Implementeremo getPossibleLists(for:)
e completeResolveListName(with:for:with:)
nella classe base ListOMatIntentsHandler
.
getPossibleLists(for:)
deve cercare di abbinare il titolo che Siri ci passa con i nomi degli elenchi effettivi.
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 }
Esaminiamo tutte le nostre liste. Se otteniamo una corrispondenza esatta, la restituiremo e, in caso contrario, restituiremo una serie di possibilità. In questa funzione, stiamo semplicemente controllando per vedere se la parola detta dall'utente è contenuta nel nome di una lista (quindi, una corrispondenza piuttosto semplice). Ciò consente a "Grocery" di abbinare "Grocery Store". Un algoritmo più avanzato potrebbe provare ad abbinare in base a parole che suonano allo stesso modo (ad esempio, con l'algoritmo Soundex),
completeResolveListName(with:for:with:)
è responsabile di decidere cosa fare con questo elenco di possibilità.
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)) } }
Se abbiamo una corrispondenza esatta, diciamo a Siri che ci siamo riusciti. Se abbiamo una corrispondenza inesatta, diciamo a Siri di chiedere all'utente se abbiamo indovinato.
Se abbiamo più corrispondenze, utilizziamo completion(.disambiguation(with: possibleLists))
per dire a Siri di mostrare un elenco e lasciare che l'utente ne scelga uno.
Ora che sappiamo qual è la richiesta, dobbiamo esaminare l'intera faccenda e assicurarci di poterla gestire.
Conferma: controlla tutte le tue dipendenze
In questo caso, se abbiamo risolto tutti i parametri, possiamo sempre gestire la richiesta. Le tipiche implementazioni di confirm()
potrebbero verificare la disponibilità di servizi esterni o verificare i livelli di autorizzazione.
Poiché confirm()
è opzionale, non potremmo semplicemente fare nulla e Siri presumerebbe che potremmo gestire qualsiasi richiesta con parametri risolti. Per essere espliciti, potremmo usare questo:
func confirm(intent: INSearchForNotebookItemsIntent, completion: @escaping (INSearchForNotebookItemsIntentResponse) -> Void) { completion(INSearchForNotebookItemsIntentResponse(code: .success, userActivity: nil)) }
Questo significa che possiamo gestire qualsiasi cosa.
Impugnatura: fallo
Il passaggio finale è gestire la richiesta.
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) }
Innanzitutto, troviamo l'elenco in base al titolo. A questo punto, resolveTitle
si è già assicurato che otterremo una corrispondenza esatta. Ma se c'è un problema, possiamo comunque restituire un errore.
Quando si verifica un errore, abbiamo la possibilità di passare un'attività dell'utente. Se la tua app utilizza Handoff e ha un modo per gestire questo tipo esatto di richiesta, Siri potrebbe provare a rinviare alla tua app per provare la richiesta lì. Non lo farà quando ci troviamo in un contesto di sola voce (ad esempio, hai iniziato con "Hey Siri") e non garantisce che lo farà in altri casi, quindi non ci contare.
Questo è ora pronto per essere testato. Scegli l'estensione dell'intento nell'elenco di destinazione in Xcode. Ma prima di eseguirlo, modifica lo schema.
Viene visualizzato un modo per fornire direttamente una query:
Si noti che sto usando "ListOMat" a causa del problema dei trattini sopra menzionato. Fortunatamente, è pronunciato come il nome della mia app, quindi non dovrebbe essere un grosso problema.
Di nuovo nell'app, ho creato un elenco "Negozio di alimentari" e un elenco "Negozio hardware". Se chiedo a Siri l'elenco dei "negozi", passerà attraverso il percorso di disambiguazione, che assomiglia a questo:
Se dici "Negozio di alimentari", otterrai una corrispondenza esatta, che va direttamente ai risultati.
Aggiunta di elementi tramite Siri
Ora che conosciamo i concetti di base di risoluzione, conferma e gestione, possiamo aggiungere rapidamente un'intenzione per aggiungere un elemento a un elenco.
Innanzitutto, aggiungi INAddTasksIntent
al plist dell'estensione:
Quindi, aggiorna la nostra funzione handle
di IntentHandler
.
override func handler(for intent: INIntent) -> Any? { switch intent { case is INSearchForNotebookItemsIntent: return SearchItemsIntentHandler() case is INAddTasksIntent: return AddItemsIntentHandler() default: return nil } }
Aggiungi uno stub per la nuova classe:
class AddItemsIntentHandler: ListOMatIntentsHandler, INAddTasksIntentHandling { }
L'aggiunta di un elemento richiede una resolve
simile per la ricerca, tranne che con un elenco di attività di destinazione anziché un titolo.
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
è proprio come completeResolveListName
, ma con tipi leggermente diversi (un elenco di attività anziché il titolo di un elenco di attività).
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)) } }
Ha la stessa logica di disambiguazione e si comporta esattamente allo stesso modo. Dire "Negozio" deve essere disambiguato e dire "Negozio di alimentari" sarebbe una corrispondenza esatta.
Lasceremo confirm
non implementata e accetteremo l'impostazione predefinita. Per handle
, dobbiamo aggiungere un elemento all'elenco e salvarlo.
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) }
Otteniamo un elenco di elementi e un elenco di obiettivi. Cerchiamo l'elenco e aggiungiamo gli articoli. Dobbiamo anche preparare una risposta per Siri da mostrare con gli elementi aggiunti e inviarla alla funzione di completamento.
Questa funzione può gestire una frase come "In ListOMat, aggiungi le mele alla lista della spesa". Può anche gestire un elenco di elementi come "riso, cipolle e olive".
Quasi fatto, solo qualche impostazione in più
Tutto questo funzionerà nel tuo simulatore o dispositivo locale, ma se vuoi inviarlo, dovrai aggiungere una chiave NSSiriUsageDescription
al plist
della tua app, con una stringa che descrive per cosa stai usando Siri. Qualcosa come "Le tue richieste sugli elenchi verranno inviate a Siri" va bene.
Dovresti anche aggiungere una chiamata a:
INPreferences.requestSiriAuthorization { (status) in }
Inseriscilo nel viewDidLoad
del tuo controller di visualizzazione principale per chiedere all'utente l'accesso a 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).
Sommario
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.
Ulteriori letture
- “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.