Czy intencje SiriKit będą pasować do Twojej aplikacji? Jeśli tak, oto jak z nich korzystać

Opublikowany: 2022-03-10
Szybkie podsumowanie ↬ Od zeszłego roku można dodać obsługę Siri do aplikacji, jeśli pasuje ona do jednego z predefiniowanych przypadków użycia Apple. Dowiedz się, czy SiriKit zadziała dla Ciebie i jak z niego korzystać.

Od wersji iOS 5 Siri pomaga użytkownikom iPhone'ów wysyłać wiadomości, ustawiać przypomnienia i wyszukiwać restauracje za pomocą aplikacji Apple. Począwszy od iOS 10, mogliśmy używać Siri również w niektórych naszych własnych aplikacjach.

Aby korzystać z tej funkcji, Twoja aplikacja musi pasować do predefiniowanych „domen i intencji” Siri. W tym artykule dowiemy się, co to jest i czy nasze aplikacje mogą z nich korzystać. Weźmiemy prostą aplikację, która jest menedżerem listy rzeczy do zrobienia i nauczymy się, jak dodać obsługę Siri. Przejrzymy również wytyczne dla programistów witryny Apple dotyczące konfiguracji i kodu Swift dla nowego typu rozszerzenia, który został wprowadzony w SiriKit: rozszerzenie Intents .

Kiedy przejdziesz do części tego artykułu dotyczącej kodowania, będziesz potrzebował Xcode (przynajmniej w wersji 9.x) i dobrze byłoby, gdybyś był zaznajomiony z rozwojem iOS w Swift, ponieważ zamierzamy dodać Siri do małego roboczego aplikacja. Przejdziemy przez etapy konfigurowania rozszerzenia na stronie programisty Apple i dodawania kodu rozszerzenia Siri do aplikacji.

„Hej Siri, dlaczego cię potrzebuję?”

Czasami korzystam z telefonu na kanapie, mając wolne obie ręce, i mogę skupić całą uwagę na ekranie. Może wyślę do siostry SMS-a, żeby zaplanować urodziny mamy lub odpowiem na pytanie w Trello. Widzę aplikację. Mogę dotknąć ekranu. Mogę pisać.

Ale być może spaceruję po mieście, słuchając podcastu, kiedy na zegarku pojawia się wiadomość. W kieszeni mam telefon, a idąc nie mogę odebrać telefonu.

Więcej po skoku! Kontynuuj czytanie poniżej ↓

Dzięki Siri mogę przytrzymać przycisk sterowania na słuchawkach i powiedzieć: „Napisz do mojej siostry, że będę tam o drugiej”. Siri jest świetne, gdy jesteś w podróży i nie możesz poświęcić pełnej uwagi swojemu telefonowi lub gdy interakcja jest niewielka, ale wymaga kilku stuknięć i pisania.

To jest w porządku, jeśli chcę używać aplikacji Apple do tych interakcji. Ale niektóre kategorie aplikacji, takie jak wiadomości, mają bardzo popularne alternatywy. Inne czynności, takie jak rezerwacja przejazdu lub rezerwacja stolika w restauracji, nie są nawet możliwe dzięki wbudowanym aplikacjom Apple, ale są idealne dla Siri.

Podejście Apple do asystentów głosowych

Aby włączyć Siri w aplikacjach firm trzecich, Apple musiało zdecydować się na mechanizm, który będzie pobierał dźwięk z głosu użytkownika i jakoś przekazywał go do aplikacji w taki sposób, aby mógł spełnić żądanie. Aby było to możliwe, Apple wymaga, aby użytkownik wymienił nazwę aplikacji w żądaniu, ale miał kilka opcji, co zrobić z resztą żądania.

  • Mógł wysłać plik dźwiękowy do aplikacji.
    Zaletą tego podejścia jest to, że aplikacja może próbować obsłużyć dosłownie każde żądanie użytkownika. Amazon lub Google mogły polubić to podejście, ponieważ mają już zaawansowane usługi rozpoznawania głosu. Ale większość aplikacji nie byłaby w stanie sobie z tym bardzo łatwo poradzić.
  • Mógł zamienić mowę w tekst i wysłać ją.
    Ponieważ wiele aplikacji nie ma zaawansowanych implementacji w języku naturalnym, użytkownik zwykle musiałby trzymać się bardzo konkretnych fraz, a zaimplementowanie obsługi w języku innym niż angielski należałoby do dewelopera aplikacji.
  • Mogło poprosić Cię o dostarczenie listy zwrotów, które rozumiesz.
    Ten mechanizm jest bliższy temu, co Amazon robi z Alexą (w ramach „umiejętności”) i umożliwia znacznie więcej zastosowań Alexy, niż może obecnie obsłużyć SiriKit. W umiejętności Alexa dostarczasz frazy ze zmiennymi zastępczymi, które Alexa wypełni za Ciebie. Na przykład „Alexa, przypomnij mi o $TIME$ to $REMINDER$ ” — Alexa porówna to zdanie z tym, co powiedział użytkownik i poda wartości TIME i REMINDER . Podobnie jak w przypadku poprzedniego mechanizmu, programista musi wykonać całe tłumaczenie i nie ma dużej elastyczności, jeśli użytkownik powie coś nieco innego.
  • Może zdefiniować listę żądań z parametrami i wysłać do aplikacji uporządkowane żądanie.
    Tak właśnie robi Apple, a korzyścią jest to, że może obsługiwać różne języki i wykonuje całą pracę, aby spróbować zrozumieć wszystkie sposoby, w jakie użytkownik może sformułować żądanie. Dużym minusem jest to, że możesz zaimplementować programy obsługi tylko dla żądań zdefiniowanych przez Apple. Jest to świetne, jeśli masz na przykład aplikację do przesyłania wiadomości, ale jeśli masz usługę strumieniowego przesyłania muzyki lub odtwarzacz podcastów, nie masz teraz możliwości korzystania z SiriKit.

Podobnie istnieją trzy sposoby, dzięki którym aplikacje mogą odpowiedzieć użytkownikowi: dźwiękiem, konwertowanym tekstem lub wyrażaniem tego, co chcesz powiedzieć, i pozwalanie systemowi na znalezienie dokładnego sposobu wyrażenia tego. Ostatnie rozwiązanie (które robi Apple) nakłada ciężar tłumaczenia na Apple, ale daje ograniczone możliwości używania własnych słów do opisywania rzeczy.

Rodzaje żądań, które możesz obsłużyć, są zdefiniowane w domenach i intencjach SiriKit. Zamiar to rodzaj prośby, którą może wysłać użytkownik, na przykład wysłanie SMS-a do kontaktu lub znalezienie zdjęcia. Każda intencja ma listę parametrów — na przykład wysyłanie SMS-ów wymaga kontaktu i wiadomości.

Domena to tylko grupa powiązanych intencji. Zarówno czytanie, jak i wysyłanie SMS-ów znajdują się w domenie wiadomości. Rezerwacja przejazdu i uzyskanie lokalizacji znajdują się w domenie rezerwacji przejazdu. Są domeny do wykonywania połączeń VoIP, rozpoczynania treningów, wyszukiwania zdjęć i kilku innych rzeczy. Dokumentacja SiriKit zawiera pełną listę domen i ich intencji.

Powszechną krytyką Siri jest to, że wydaje się, że nie jest w stanie obsłużyć żądań tak samo jak Google i Alexa, a ekosystem głosowy innych firm, który umożliwia konkurencja Apple, jest bogatszy.

Zgadzam się z tą krytyką. Jeśli Twoja aplikacja nie pasuje do bieżących zamiarów, nie możesz używać SiriKit i nic nie możesz zrobić. Nawet jeśli Twoja aplikacja pasuje, nie możesz kontrolować wszystkich słów, które Siri mówi lub rozumie; więc jeśli masz określony sposób mówienia o rzeczach w swojej aplikacji, nie zawsze możesz tego nauczyć Siri.

Twórcy iOS mają nadzieję, że Apple znacznie rozszerzy swoją listę intencji i że przetwarzanie języka naturalnego stanie się znacznie lepsze. Jeśli tak się stanie, będziemy mieli asystenta głosowego, który działa bez konieczności tłumaczenia przez programistów lub rozumienia wszystkich sposobów mówienia tego samego. Zaimplementowanie obsługi zapytań strukturalnych jest w rzeczywistości dość proste — o wiele łatwiejsze niż budowanie parsera języka naturalnego.

Kolejną dużą zaletą struktury intencji jest to, że nie ogranicza się ona do Siri i żądań głosowych. Nawet teraz aplikacja Mapy może generować żądanie aplikacji oparte na intencji (na przykład rezerwację w restauracji). Robi to programowo (nie z głosu ani języka naturalnego). Gdyby Apple pozwoliło aplikacjom na wzajemne wykrywanie ujawnionych intencji, mielibyśmy znacznie lepszy sposób na współpracę aplikacji (w przeciwieństwie do adresów URL w stylu x-callback).

Wreszcie, ponieważ intencja jest uporządkowanym żądaniem z parametrami, aplikacja może w prosty sposób wyrazić, że brakuje parametrów lub że potrzebuje pomocy w rozróżnieniu niektórych opcji. Siri może następnie zadawać dalsze pytania w celu rozwiązania parametrów bez konieczności prowadzenia rozmowy przez aplikację.

Domena rezerwacji przejazdów

Aby zrozumieć domeny i intencje, przyjrzyjmy się domenie rezerwacji przejazdów. Jest to domena, której użyjesz, aby poprosić Siri o zakup samochodu Lyft.

Apple definiuje, jak poprosić o jazdę i jak uzyskać informacje na ten temat, ale w rzeczywistości nie ma wbudowanej aplikacji Apple, która mogłaby obsłużyć to żądanie. Jest to jedna z niewielu domen, w których wymagana jest aplikacja obsługująca SiriKit.

Możesz wywołać jedną z intencji głosowo lub bezpośrednio z Map. Oto niektóre z intencji tej domeny:

  • Poproś o przejazd
    Użyj tego, aby zarezerwować przejazd. Musisz podać miejsce odbioru i zwrotu, a aplikacja może również potrzebować informacji o wielkości Twojej grupy i rodzaju jazdy, którą chcesz. Przykładowe wyrażenie może brzmieć: „Zarezerwuj przejazd z <appname>”.
  • Uzyskaj status przejazdu
    Użyj tej intencji, aby dowiedzieć się, czy Twoja prośba została odebrana i uzyskać informacje o pojeździe i kierowcy, w tym o ich lokalizacji. Aplikacja Mapy wykorzystuje tę intencję do pokazania zaktualizowanego obrazu zbliżającego się do Ciebie samochodu.
  • Anuluj jazdę
    Użyj tego, aby anulować zarezerwowany przejazd.

W każdym z tych zamiarów Siri może potrzebować więcej informacji. Jak zobaczysz, gdy zaimplementujemy moduł obsługi intencji, twoje rozszerzenie Intents może poinformować Siri, że brakuje wymaganego parametru, a Siri poprosi o to użytkownika.

Fakt, że intencje mogą być wywoływane programowo przez Mapy, pokazuje, w jaki sposób intencje mogą umożliwić komunikację między aplikacjami w przyszłości.

Uwaga : możesz uzyskać pełną listę domen i ich intencji na stronie programisty Apple. Istnieje również przykładowa aplikacja Apple z zaimplementowanymi wieloma domenami i intencjami, w tym rezerwacją przejazdów.

Dodawanie list i obsługi domen notatek do Twojej aplikacji

OK, teraz, gdy rozumiemy podstawy SiriKit, spójrzmy, jak byś dodał obsługę Siri w aplikacji, która wymaga dużej ilości konfiguracji i klasy dla każdej intencji, którą chcesz obsłużyć.

Pozostała część tego artykułu zawiera szczegółowe kroki, aby dodać obsługę Siri do aplikacji. Jest pięć ważnych rzeczy, które musisz zrobić:

  1. Przygotuj się do dodania nowego rozszerzenia do aplikacji, tworząc profile aprowizacji z nowymi uprawnieniami w witrynie programisty Apple.
  2. Skonfiguruj swoją aplikację (za pomocą jej plist ), aby korzystać z uprawnień.
  3. Użyj szablonu Xcode, aby rozpocząć pracę z przykładowym kodem.
  4. Dodaj kod, aby wspierać intencje Siri.
  5. Skonfiguruj słownictwo Siri za pomocą plist s.

Nie martw się: omówimy każdy z nich, wyjaśniając po drodze rozszerzenia i uprawnienia.

Aby skupić się tylko na częściach Siri, przygotowałem prosty menedżer list rzeczy do zrobienia, List-o-Mat.

Animowany GIF przedstawiający demo List-o-Mat
Tworzenie list w List-o-Mat (duży podgląd)

Pełne źródło próbki, List-o-Mat, można znaleźć w serwisie GitHub.

Aby go stworzyć, wszystko, co zrobiłem, to zacząłem od szablonu aplikacji Xcode Master-Detail i przekształciłem oba ekrany w UITableView . Dodałem sposób na dodawanie i usuwanie list i elementów oraz sposób na zaznaczanie elementów jako wykonanych. Cała nawigacja jest generowana przez szablon.

Do przechowywania danych użyłem protokołu Codable (wprowadzonego na WWDC 2017), który zamienia struktury na JSON i zapisuje je w pliku tekstowym w folderze documents .

Celowo zachowałem bardzo prosty kod. Jeśli masz jakieś doświadczenie z Swiftem i tworzeniem kontrolerów widoku, nie powinieneś mieć z tym problemu.

Teraz możemy przejść przez etapy dodawania obsługi SiriKit. Kroki wysokiego poziomu byłyby takie same dla każdej aplikacji i niezależnie od domeny i intencji, które planujesz wdrożyć. Zajmiemy się głównie witryną programisty Apple, edycją plist i pisaniem trochę Swifta.

W przypadku List-o-Mat skupimy się na domenie list i notatek, która ma szerokie zastosowanie w aplikacjach do robienia notatek i listach rzeczy do zrobienia.

W domenie list i notatek mamy następujące intencje, które miałyby sens dla naszej aplikacji.

  • Pobierz listę zadań.
  • Dodaj nowe zadanie do listy.

Ponieważ interakcje z Siri faktycznie mają miejsce poza Twoją aplikacją (może nawet wtedy, gdy Twoja aplikacja nie jest uruchomiona), iOS używa rozszerzenia, aby to zaimplementować.

Rozszerzenie intencji

Jeśli nie pracowałeś z rozszerzeniami, musisz wiedzieć trzy główne rzeczy:

  1. Rozszerzenie to odrębny proces. Jest dostarczany w pakiecie Twojej aplikacji, ale działa całkowicie samodzielnie, z własną piaskownicą.
  2. Twoja aplikacja i rozszerzenie mogą komunikować się ze sobą, będąc w tej samej grupie aplikacji. Najprostszym sposobem jest skorzystanie z udostępnionych folderów piaskownicy grupy (dzięki czemu mogą czytać i zapisywać te same pliki, jeśli je tam umieścisz).
  3. Rozszerzenia wymagają własnych identyfikatorów aplikacji, profili i uprawnień.

Aby dodać rozszerzenie do swojej aplikacji, zacznij od zalogowania się na swoje konto programisty i przejścia do sekcji „Certyfikaty, identyfikatory i profile”.

Aktualizowanie danych konta Apple Developer App

Na naszym koncie programisty Apple pierwszą rzeczą, którą musimy zrobić, to utworzyć grupę aplikacji. Przejdź do sekcji „Grupy aplikacji” w sekcji „Identyfikatory” i dodaj jeden.

Zrzut ekranu okna dialogowego witryny programisty Apple do rejestracji grupy aplikacji
Rejestracja grupy aplikacji (duży podgląd)

Musi zaczynać się od group , po której następuje zwykły identyfikator oparty na odwrotnej domenie. Ponieważ ma prefiks, możesz użyć identyfikatora swojej aplikacji do reszty.

Następnie musimy zaktualizować identyfikator naszej aplikacji, aby korzystać z tej grupy i włączyć Siri:

  1. Przejdź do sekcji „Identyfikatory aplikacji” i kliknij identyfikator swojej aplikacji;
  2. Kliknij przycisk „Edytuj”;
  3. Włącz grupy aplikacji (jeśli nie jest włączone dla innego rozszerzenia).
    Zrzut ekranu witryny dewelopera Apple umożliwiającej grupowanie aplikacji dla identyfikatora aplikacji
    Włącz grupy aplikacji (duży podgląd)
  4. Następnie skonfiguruj grupę aplikacji, klikając przycisk „Edytuj”. Wybierz wcześniej grupę aplikacji.
    Zrzut ekranu okna dialogowego witryny programisty Apple, aby ustawić nazwę grupy aplikacji
    Ustaw nazwę grupy aplikacji (duży podgląd)
  5. Włącz SiriKit.
    Zrzut ekranu włączania SiriKit
    Włącz SiriKit (duży podgląd)
  6. Kliknij „Gotowe”, aby go zapisać.

Teraz musimy utworzyć nowy identyfikator aplikacji dla naszego rozszerzenia:

  1. W tej samej sekcji „Identyfikatory aplikacji” dodaj nowy identyfikator aplikacji. To będzie identyfikator Twojej aplikacji z przyrostkiem. Nie używaj samego Intents jako sufiksu, ponieważ ta nazwa stanie się nazwą twojego modułu w Swift i będzie wtedy kolidować z prawdziwymi Intents .
    Zrzut ekranu ekranu programisty Apple, aby utworzyć identyfikator aplikacji
    Utwórz identyfikator aplikacji dla rozszerzenia Intents (duży podgląd)
  2. Włącz ten identyfikator aplikacji również dla grup aplikacji (i skonfiguruj grupę tak jak wcześniej).

Teraz utwórz programistyczny profil aprowizacji dla rozszerzenia Intents i ponownie wygeneruj profil aprowizacji aplikacji. Pobierz i zainstaluj je tak, jak zwykle.

Teraz, gdy nasze profile są zainstalowane, musimy przejść do Xcode i zaktualizować uprawnienia aplikacji.

Aktualizacja uprawnień aplikacji w Xcode

Wróć do Xcode, wybierz nazwę swojego projektu w nawigatorze projektów. Następnie wybierz główny cel swojej aplikacji i przejdź do zakładki „Możliwości”. Tam zobaczysz przełącznik, aby włączyć obsługę Siri.

Zrzut ekranu ekranu uprawnień Xcode pokazujący, że SiriKit jest włączony
Włącz SiriKit w uprawnieniach swojej aplikacji. (duży podgląd)

W dalszej części listy możesz włączyć grupy aplikacji i skonfigurować je.

Zrzut ekranu ekranu uprawnień Xcode pokazujący, że grupa aplikacji jest włączona i skonfigurowana
Skonfiguruj grupę aplikacji aplikacji (duży podgląd)

Jeśli poprawnie skonfigurowałeś, zobaczysz to w pliku .entitlements aplikacji:

Zrzut ekranu z plistą aplikacji pokazujący, że uprawnienia są ustawione
Plist pokazuje uprawnienia, które ustawiłeś (duży podgląd)

Teraz jesteśmy w końcu gotowi do dodania celu rozszerzenia Intents do naszego projektu.

Dodawanie rozszerzenia Intencje

Jesteśmy w końcu gotowi do dodania rozszerzenia. W Xcode wybierz „Plik” → „Nowy cel”. Ten arkusz wyskoczy:

Zrzut ekranu przedstawiający rozszerzenie Intents w oknie dialogowym Nowy cel w Xcode
Dodaj rozszerzenie Intents do swojego projektu (duży podgląd)

Wybierz „Rozszerzenie intencji” i kliknij przycisk „Dalej”. Wypełnij następujący ekran:

Zrzut ekranu z Xcode pokazujący, jak skonfigurować rozszerzenie Intents
Skonfiguruj rozszerzenie Intents (duży podgląd)

Nazwa produktu musi być zgodna z sufiksem wprowadzonym w identyfikatorze aplikacji intencji w witrynie programisty Apple.

Zdecydowaliśmy się nie dodawać rozszerzenia interfejsu użytkownika intencji. Nie jest to omówione w tym artykule, ale możesz dodać to później, jeśli potrzebujesz. Zasadniczo jest to sposób na umieszczenie własnego brandingu i stylu wyświetlania w wynikach wizualnych Siri.

Kiedy skończysz, Xcode utworzy klasę obsługi intencji, której możemy użyć jako części początkowej naszej implementacji Siri.

Obsługa intencji: Rozwiąż, potwierdź i obsługuj

Xcode wygenerował nowy cel, który ma dla nas punkt wyjścia.

Pierwszą rzeczą, którą musisz zrobić, to skonfigurować nowy cel tak, aby znajdował się w tej samej grupie aplikacji, co aplikacja. Tak jak poprzednio, przejdź do zakładki „Możliwości” celu, włącz grupy aplikacji i skonfiguruj je za pomocą nazwy grupy. Pamiętaj, że aplikacje w tej samej grupie mają piaskownicę, której mogą używać do udostępniania sobie plików. Potrzebujemy tego, aby żądania Siri trafiały do ​​naszej aplikacji.

List-o-Mat posiada funkcję, która zwraca folder dokumentu grupy. Powinniśmy go używać za każdym razem, gdy chcemy czytać lub pisać do udostępnionego pliku.

 func documentsFolder() -> URL? { return FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.app-o-mat.ListOMat") }

Na przykład, gdy zapisujemy listy, używamy tego:

 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 }

Szablon rozszerzenia Intents utworzył plik o nazwie IntentHandler.swift z klasą o nazwie IntentHandler . Skonfigurował go również jako punkt wejścia intencji w plist rozszerzenia .

Zrzut ekranu z Xcode pokazujący, jak IntentHandler jest skonfigurowany jako punkt wejścia
Plist rozszerzenia intencji konfiguruje IntentHandler jako punkt wejścia

W tej samej plist zobaczysz sekcję, w której możesz zadeklarować intencje, które wspieramy. Zaczniemy od tej, która umożliwia wyszukiwanie list, która nazywa się INSearchForNotebookItemsIntent . Dodaj go do tablicy w obszarze IntentsSupported .

Zrzut ekranu w Xcode pokazujący, że plik plist rozszerzenia powinien zawierać listę obsługiwanych intencji
Dodaj nazwę intencji do listy intencji (duży podgląd)

Teraz przejdź do IntentHandler.swift i zastąp jego zawartość następującym kodem:

 import Intents class IntentHandler: INExtension { override func handler(for intent: INIntent) -> Any? { switch intent { case is INSearchForNotebookItemsIntent: return SearchItemsIntentHandler() default: return nil } } }

Funkcja handler jest wywoływana, aby uzyskać obiekt do obsługi określonej intencji. Możesz po prostu zaimplementować wszystkie protokoły w tej klasie i zwrócić self , ale umieścimy każdą intencję w osobnej klasie, aby była lepiej zorganizowana.

Ponieważ zamierzamy mieć kilka różnych klas, dajmy im wspólną klasę bazową dla kodu, który musimy dzielić między nimi:

 class ListOMatIntentsHandler: NSObject { }

Struktura intencji wymaga od nas dziedziczenia z NSObject . Niektóre metody wypełnimy później.

Naszą implementację wyszukiwania rozpoczynamy od tego:

 class SearchItemsIntentHandler: ListOMatIntentsHandler, INSearchForNotebookItemsIntentHandling { }

Aby ustawić moduł obsługi intencji, musimy wykonać trzy podstawowe kroki

  1. Rozwiąż parametry.
    Upewnij się, że podano wymagane parametry i odróżnij te, których nie rozumiesz w pełni.
  2. Potwierdź , że żądanie jest wykonalne.
    Jest to często opcjonalne, ale nawet jeśli wiesz, że każdy parametr jest dobry, nadal możesz potrzebować dostępu do zasobu zewnętrznego lub mieć inne wymagania.
  3. Obsłuż żądanie.
    Zrób to, o co prosisz.

INSearchForNotebookItemsIntent , pierwsza intencja, którą zaimplementujemy, może służyć do wyszukiwania zadań. Rodzaje żądań, z którymi możemy sobie poradzić, to „W List-o-Mat, pokaż listę sklepów spożywczych” lub „W List-o-Mat, pokaż listę sklepów”.

Na bok: „List-o-Mat” to w rzeczywistości zła nazwa aplikacji SiriKit, ponieważ Siri ma trudności z myślnikami w aplikacjach. Na szczęście SiriKit pozwala nam mieć alternatywne nazwy i zapewniać wymowę. W aplikacji Info.plist dodaj tę sekcję:

Zrzut ekranu z Xcode pokazujący, że lista aplikacji może dodawać alternatywne nazwy aplikacji i wymowy
Dodaj alternatywne nazwy aplikacji i przewodniki wymowy do plisty aplikacji

Dzięki temu użytkownik może powiedzieć „list oh mat” i rozumieć to jako jedno słowo (bez myślników). Nie wygląda to idealnie na ekranie, ale bez niego Siri czasami myśli, że „Lista” i „Mata” to osobne słowa i bardzo się myli.

Rozwiązanie: określenie parametrów

Aby wyszukać elementy notatnika, istnieje kilka parametrów:

  1. typ pozycji (zadanie, lista zadań lub notatka),
  2. tytuł towaru,
  3. zawartość towaru,
  4. status ukończenia (czy zadanie jest oznaczone jako wykonane, czy nie),
  5. lokalizacja, z którą jest powiązany,
  6. data, z którą jest powiązany.

Potrzebujemy tylko dwóch pierwszych, więc będziemy musieli napisać dla nich funkcje rozwiązywania. INSearchForNotebookItemsIntent zawiera metody, które możemy zaimplementować.

Ponieważ zależy nam tylko na wyświetlaniu list zadań, zakodujemy to na stałe w rozwiązaniu typu elementu. W SearchItemsIntentHandler dodaj to:

 func resolveItemType(for intent: INSearchForNotebookItemsIntent, with completion: @escaping (INNotebookItemTypeResolutionResult) -> Void) { completion(.success(with: .taskList)) }

Tak więc bez względu na to, co powie użytkownik, będziemy szukać list zadań. Gdybyśmy chcieli rozszerzyć naszą obsługę wyszukiwania, pozwolilibyśmy Siri spróbować to rozgryźć na podstawie oryginalnej frazy, a następnie użyć completion(.needsValue()) , jeśli brakuje typu elementu. Alternatywnie moglibyśmy spróbować odgadnąć tytuł, sprawdzając, co do niego pasuje. W tym przypadku zakończylibyśmy sukcesem, gdy Siri wie, co to jest, i użyjemy completion(.notRequired()) , gdy zamierzamy wypróbować wiele możliwości.

Rozdzielczość tytułu jest nieco trudniejsza. Chcemy, aby Siri użyła listy, jeśli znajdzie taką, która dokładnie pasuje do tego, co powiedziałeś. Jeśli nie jest to pewne lub istnieje więcej niż jedna możliwość, chcemy, aby Siri poprosił nas o pomoc w rozwiązaniu tego problemu. Aby to zrobić, SiriKit zapewnia zestaw wyliczeń rozdzielczości, które pozwalają nam wyrazić to, co chcemy, aby wydarzyło się dalej.

Tak więc, jeśli powiesz „Sklep spożywczy”, Siri będzie dokładnie pasować. Ale jeśli powiesz „Sklep”, Siri wyświetli menu pasujących list.

Zaczniemy od tej funkcji, aby nadać podstawową strukturę:

 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) }

Zaimplementujemy getPossibleLists(for:) i completeResolveListName(with:for:with:) w klasie bazowej ListOMatIntentsHandler .

getPossibleLists(for:) musi spróbować rozmycie dopasować tytuł, który przekazuje nam Siri, z rzeczywistymi nazwami list.

 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 }

Przechodzimy przez wszystkie nasze listy. Jeśli otrzymamy dokładne dopasowanie, zwrócimy je, a jeśli nie, zwrócimy szereg możliwości. W tej funkcji po prostu sprawdzamy, czy słowo wypowiedziane przez użytkownika jest zawarte w nazwie listy (a więc dość proste dopasowanie). Dzięki temu „spożywczy” pasuje do „sklepu spożywczego”. Bardziej zaawansowany algorytm może próbować dopasować na podstawie słów, które brzmią tak samo (na przykład za pomocą algorytmu Soundex),

completeResolveListName(with:for:with:) jest odpowiedzialne za podjęcie decyzji, co zrobić z tą listą możliwości.

 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)) } }

Jeśli mamy dokładne dopasowanie, mówimy Siri, że nam się udało. Jeśli otrzymamy jedno niedokładne dopasowanie, mówimy Siri, aby zapytał użytkownika, czy dobrze odgadliśmy.

Jeśli otrzymaliśmy wiele dopasowań, użyjemy completion(.disambiguation(with: possibleLists)) , aby poinformować Siri, aby pokazał listę i pozwolił użytkownikowi wybrać jedną.

Teraz, gdy wiemy, o co chodzi, musimy przyjrzeć się całej sprawie i upewnić się, że sobie z nią poradzimy.

Potwierdź: Sprawdź wszystkie swoje zależności

W takim przypadku, jeśli rozwiązaliśmy wszystkie parametry, zawsze możemy obsłużyć żądanie. Typowe implementacje confirm() mogą sprawdzać dostępność usług zewnętrznych lub sprawdzać poziomy autoryzacji.

Ponieważ confirm() jest opcjonalna, po prostu nie możemy nic zrobić, a Siri założy, że możemy obsłużyć każde żądanie z rozwiązanymi parametrami. Mówiąc wprost, możemy użyć tego:

 func confirm(intent: INSearchForNotebookItemsIntent, completion: @escaping (INSearchForNotebookItemsIntentResponse) -> Void) { completion(INSearchForNotebookItemsIntentResponse(code: .success, userActivity: nil)) }

Oznacza to, że poradzimy sobie ze wszystkim.

Uchwyt: zrób to

Ostatnim krokiem jest obsługa żądania.

 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) }

Najpierw znajdujemy listę na podstawie tytułu. W tym momencie resolveTitle się już, że otrzymamy dokładne dopasowanie. Ale jeśli wystąpi problem, nadal możemy zwrócić awarię.

Gdy mamy awarię, mamy możliwość przekazania aktywności użytkownika. Jeśli Twoja aplikacja korzysta z funkcji Handoff i ma sposób na obsłużenie tego typu żądania, Siri może spróbować odroczyć w aplikacji próbę wykonania żądania w tym miejscu. Nie zrobi tego, gdy jesteśmy w kontekście tylko głosowym (na przykład zacząłeś od „Hej Siri”) i nie gwarantuje, że zrobi to w innych przypadkach, więc nie licz na to.

To jest teraz gotowe do przetestowania. Wybierz rozszerzenie intencji na liście docelowej w Xcode. Ale zanim go uruchomisz, edytuj schemat.

Zrzut ekranu z Xcode pokazujący jak edytować schemat
Edytuj schemat intencji, aby dodać przykładową frazę do debugowania.

Daje to możliwość bezpośredniego dostarczenia zapytania:

Zrzut ekranu z Xcode pokazujący okno edycji schematu
Dodaj przykładową frazę do sekcji Uruchom schematu. (duży podgląd)

Zauważ, że używam „ListOMat” z powodu wspomnianego powyżej problemu z myślnikami. Na szczęście wymawia się to tak samo, jak nazwa mojej aplikacji, więc nie powinno to stanowić większego problemu.

Wracając do aplikacji, stworzyłem listę „Sklep spożywczy” i listę „Sklep ze sprzętem”. Jeśli poproszę Siri o listę „sklepów”, przejdzie ona przez ścieżkę ujednoznacznienia, która wygląda tak:

Animowany GIF przedstawiający Siri obsługującego żądanie wyświetlenia listy Sklepu
Siri obsługuje żądanie, prosząc o wyjaśnienie. (duży podgląd)

Jeśli powiesz „Sklep spożywczy”, otrzymasz dokładne dopasowanie, które odnosi się bezpośrednio do wyników.

Dodawanie elementów za pośrednictwem Siri

Teraz, gdy znamy podstawowe koncepcje rozwiązywania, potwierdzania i obsługi, możemy szybko dodać zamiar dodania elementu do listy.

Najpierw dodaj INAddTasksIntent do plist rozszerzenia:

Zrzut ekranu w XCode pokazujący nową intencję dodawaną do plist
Dodaj INAddTasksIntent do plist rozszerzenia (duży podgląd)

Następnie zaktualizuj funkcję handle naszego IntentHandler .

 override func handler(for intent: INIntent) -> Any? { switch intent { case is INSearchForNotebookItemsIntent: return SearchItemsIntentHandler() case is INAddTasksIntent: return AddItemsIntentHandler() default: return nil } }

Dodaj skrót dla nowej klasy:

 class AddItemsIntentHandler: ListOMatIntentsHandler, INAddTasksIntentHandling { }

Dodanie elementu wymaga podobnego resolve wyszukiwania, z wyjątkiem docelowej listy zadań zamiast tytułu.

 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 jest jak completeResolveListName , ale z nieco innymi typami (lista zadań zamiast tytułu listy zadań).

 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)) } }

Ma taką samą logikę ujednoznaczniania i zachowuje się dokładnie w ten sam sposób. Powiedzenie „Sklep” musi być ujednoznaczne, a powiedzenie „Sklep spożywczy” byłoby dokładnym dopasowaniem.

Pozostawimy confirm niezaimplementowane i zaakceptujemy ustawienie domyślne. W przypadku handle , musimy dodać element do listy i zapisać go.

 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) }

Otrzymujemy listę przedmiotów i listę docelową. Przeszukujemy listę i dodajemy pozycje. Musimy również przygotować odpowiedź, aby Siri pokazała się z dodanymi elementami i wysłać ją do funkcji uzupełniania.

Ta funkcja może obsłużyć frazę typu „W ListOMat dodaj jabłka do listy zakupów”. Może również obsługiwać listę przedmiotów, takich jak „ryż, cebula i oliwki”.

Zrzut ekranu symulatora pokazujący, jak Siri dodaje produkty do listy sklepów spożywczych
Siri dodaje kilka pozycji do listy sklepów spożywczych

Prawie gotowe, jeszcze tylko kilka ustawień

Wszystko to będzie działać w Twoim symulatorze lub urządzeniu lokalnym, ale jeśli chcesz to przesłać, musisz dodać klucz NSSiriUsageDescription do plist aplikacji z ciągiem opisującym, do czego używasz Siri. Coś w rodzaju „Twoje prośby o listy zostaną wysłane do Siri” jest w porządku.

Powinieneś również dodać połączenie do:

 INPreferences.requestSiriAuthorization { (status) in }

Umieść to w viewDidLoad kontrolera głównego widoku, aby poprosić użytkownika o dostęp do Siri. This will show the message you configured above and also let the user know that they could be using Siri for this app.

A screenshot of the dialog that a device pops up when you ask for Siri permission
The device will ask for permission if you try to use Siri in the 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:

  1. Create a plist file in your app (not the extension), named AppIntentVocabulary.plist .
  2. Fill out the intents and phrases that you support.
A screenshot of the AppIntentVocabulary.plist showing sample phrases
Add an AppIntentVocabulary.plist to list the sample phrases that will invoke the intent you handle. (duży podgląd)

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).

Streszczenie

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:

  1. Make an app ID for an Intents extension.
  2. Make an app group if you don't already have one.
  3. Use the app group in the app ID for the app and extension.
  4. Add Siri support to the app's ID.
  5. Regenerate the profiles and download them.

And here are the steps in Xcode for creating Siri's Intents extension:

  1. Add an Intents extension using the Xcode template.
  2. Update the entitlements of the app and extension to match the profiles (groups and Siri support).
  3. Add your intents to the extension's plist .

And you'll need to add code to do the following things:

  1. Use the app group sandbox to communicate between the app and extension.
  2. Add classes to support each intent with resolve, confirm and handle functions.
  3. Update the generated IntentHandler to use those classes.
  4. Ask for Siri access somewhere in your app.

Finally, there are some Siri-specific configuration settings:

  1. Add the Siri support security string to your app's plist .
  2. Add sample phrases to an AppIntentVocabulary.plist file in your app.
  3. 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.

Dalsza lektura

  • “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.