Интеграция Siri или «Вот что мне удалось найти в вашем приложении»

8b5501852fd34a0084c299f5116d35eb.jpg

На WWDC 2016 Apple представила миру SiriKit — API для работы с голосовым помощником.

Если вы не смотрели WWDC сессию про SiriKit и ждёте, что сможете использовать Siri в любом приложении, то вам стоит знать, что на данный момент поддерживается всего несколько типов сервисов:

1) Аудио и видео вызовы,
2) Сообщения,
2) Платежи,
3) Поиск фото,
4) Тренировки,
5) Поездки (бронирование).

Также, как гласит документация, существуют возможность взаимодействия с автомобилем с помощью CarPlay (INSetClimateSettingsInCarIntent, INSetSeatTemperatureInCarIntent, etc.).

Таким образом, Siri можно дать команду »<позвони тому-то, отправь сообщение, поищи фото, etc.> через <название вашего приложения>».
Все устроено таким образом, что взаимодействовать напрямую с нейросетью не придется — SDK предоставляет простые протоколы и набор легковесных классов для передачи информации в методах. Разработчику остается только реализовать эти протоколы.

Для ленивых в конце статьи ссылка на демо-приложение (отправляем с помощью Siri сообщение своим друзьям из ВК).

Краеугольный камень SiriKit — Intent (андроид-разработчики, привет!). Объект класса INIntent (или его наследников) — это входные данные, которые генерирует Siri. Сам INIntent не содержит в себе ничего помимо идентификатора. Роль контекста делегирована его сабклассам. Конкретный сабкласс зависит от типа приложения. Например, для приложения с тренировками INStartWorkoutIntent содержит информацию о цели — сколько времени нужно провести за тем или иным упражнением, где происходит тренировка и так далее. Для фото сервиса можно использовать интент INSearchForPhotosIntent, в котором содержится геотег (CLPlacemark), дата создания фото, список людей на фото и т.д.

Для обработки входящего интента разработчику понадобится создавать объекты класса INIntentResolutionResult (или его сабклассов).

Существует три стадии обработки входной информации:

  • уточнение (resolve),
  • подтверждение (confirm),
  • выполнение действия (handle).


На каждой из этих стадий мы получаем интент и должны возвращать соответствующий INIntentResolutionResult.

Например, если мы разрабатываем приложение-месседжер, то на стадии уточнения мы однозначно определяем пользователей, которым мы отправляем сообщение (resolveRecipientsForSendMessage), содержимое сообщения (resolveContentForSendMessage). На стадии подтверждения мы проверяем (confirmSendMessage), что все готово для отправки (например, пользователь авторизован). И наконец, на стадии выполнения (handleSendMessage), мы отправляем сообщение выбранным адресатам.

На любом этапе есть два варианта развития сценария: позитивный и негативный. Выбор пути делегирован разработчику: Siri предоставляет обработанные данные в виде объекта интента, а какой результат вернуть системе решает программист.

Intents Extension


Чтобы ваше приложение поддерживало работу с Siri понадобится добавить в InfoPlist ключ NSSiriUsageDescription с текстом, поясняющим конечному пользователю, зачем вашему приложению доступ к Siri (по аналогии с NSLocationUsageDescription для геолокации).

c535e3851eda49e98d77684324eb198d.png

И запросить permission:

[INPreferences requestSiriAuthorization:^(INSiriAuthorizationStatus status) {
}];


После этого нужно добавить в проект таргет с типом IntentsExtension.

1222220f0e8f429eaff45d752b0bda2e.png

После добавления таргета, обратите внимание на его структуру. Входная точка для интентов — объект класса INExtension. Внутри экземпляра класса в методе (handlerForIntent) выбирается какой объект будет обрабатывать входящий интент. В InfoPlist таргета прописываются название класса-наследника INExtension и поддерживаемые типы интентов. Также можно указать, какие из типов интентов будут недоступны на заблокированном экране.

dbdf7b799501449a957b42e6e65ee444.png

Для обработки конкретного типа интента требуется реализовать специализированный протокол в вашем классе (например, INSendMessageIntentHandling). Протокол содержит в себе методы, необходимые для прохождения вышеописанных этапов (resolve, confirm, handle).

Рассмотрим реализацию класса обработчика для интента отправки сообщения.

Как было описано выше, на первом этапе (уточнение) требуется однозначно определить пользователей и содержимое сообщения. Если говорить о контенте сообщения, то в большинстве случаев достаточно знать, что оно не пустое. Но с адресатами не всё так просто. В интенте мы можем получить список пользователей, которых распознала Siri. Это массив объектов класса INPerson. Для каждого адресата необходимо найти соответствие в списке существующих адресатов.

Существует три варианта развития событий:

  • соответствия нет — негативный сценарий;
  • одно соответствие — однозначность подтверждена;
  • более одного соответствия — нужно предоставить пользователю возможность выбрать правильный контакт из списка (INPersonResolutionResult → disambiguationWithPeopleToDisambiguate).


Примечание — после выбора пользователя в случае disambiguation, повторно будет выполнен метод resolveRecipientsForSendMessage. При проверке совпадения пользователей нужно учитывать, что метод может быть вызван несколько раз.

На этапе подтверждения мы проверяем необходимые условия для возможности отправки сообщения. И наконец, в методе handleSendMessage (финальная стадия) выполняется отправка сообщения. На каждом из этапов есть позитивный и негативный сценарий работы.

Siri не всегда понимает, что мы хотим сказать. Чтобы ей помочь, можно использовать Vocabulary — словарь терминов. Они бывают двух видов: статические и динамические. В первом случае мы предоставляем специальный AppIntentVocabulary.plist, в котором записаны общие термины. Динамический словарь заполняется специализированными терминами для текущего пользователя:

[[INVocabulary sharedVocabulary] setVocabularyStrings:[usersNames copy] ofType:INVocabularyStringTypeContactName];


За словарь отвечает не Intent Extension, а основное приложение. То есть свои дополнительные термины нужно указать до запуска расширения Siri.

Intents UI Extension


Помимо логики обработки данных, SiriKit предоставляет возможность изменить интерфейс отображения данных. Для этого существует Intents UI Extension. По аналогии с Intents Extension, InfoPlist UI-таргета содержит в себе список поддерживаемых интентов, а также название storyboard-файла.

1493ce50ea9e4f31b2b0d55fe9fe088a.png

Примечание — для любого интента в UI-расширении будет создан один и тот же контроллер (entry point в storyboard). Для делегирования логики отображения рекомендуется использовать дочерние контроллеры (child view controllers). Базовый контроллер должен поддерживать протокол INUIHostedViewControlling, в котором определен метод конфигурирования интерфейса — configureWithInteraction. Рассмотрим параметры этого метода.

  • interaction (INInteraction) содержит интент и статус его обработки. По классу передаваемого интента мы можем инстанцировать соответствующий контроллер;
  • context (INUIHostedViewContext) позволяет определить, используется ли Siri напрямую, или же задействуются Maps;
  • completion (блок завершения), в который требуется передать размер (CGSize) отображаемого контроллера (сниппета). Размер ограничен минимумом (hostedViewMinimumAllowedSize) и максимумом (hostedViewMaximumAllowedSize), значения которых определены системой в категории NSExtensionContext (INUIHostedViewControlling).


Помимо основного протокола INUIHostedViewControlling, существует дополнительный протокол INUIHostedViewSiriProviding, который позволяет контролировать отображение стандартных интерфейсов Siri для сообщений (displaysMessage) и карт (displaysMap). В требованиях Apple по созданию интерфейсов для Siri есть запрет на отображение в сниппетах рекламы. Если вы планируете использовать анимацию, то рекомендуется выполнять её во viewDidAppear.

Для передачи данных между основным приложением и экстеншенами как и прежде нужно использовать Application groups (пример в демо).

Скриншоты


ad40d4b142c24973a8015818054c35ef.jpg

ff3ea75e5d7f44748f74ffe10ba32478.jpg

Ссылки


SiriKit Programming Guide
Демо-приложение

© Habrahabr.ru