Знакомство с expo-app-intents: Siri, Быстрые команды, Spotlight и Apple Intelligence для приложений на Expo

Источник: Expo Blog•

Знакомство с expo-app-intents: Siri, Быстрые команды, Spotlight и Apple Intelligence для приложений на Expo

expo-app-intents переносит App Intents от Apple в приложения на Expo, позволяя пользователям взаимодействовать с вашим приложением через Siri, Быстрые команды, Spotlight и Apple Intelligence.

Мы рекомендуем выполнять сборку с использованием Xcode 27 и тестирование на устройстве под управлением iOS 27. Пакет поддерживает iOS 16.4 и более поздние версии, однако для некоторых функций в этом руководстве требуются Xcode 27 или iOS 27.

expo-app-intents позволяет пользователям использовать ваше приложение через Siri и Быстрые команды, находить его контент в Spotlight и передавать Apple Intelligence контекст о том, что отображается на экране. Приложение ресторана может предлагать блюда в качестве параметров быстрых команд. Почтовое приложение может предоставлять действия с почтой и определять письмо, которое читает пользователь.

Интеграция фреймворка App Intents от Apple в приложение на Expo сопряжена с двумя сложностями. Apple обнаруживает объявления намерений во время сборки приложения в Xcode, поэтому они должны существовать на Swift до запуска приложения. Кроме того, намерение может выполниться до запуска JavaScript, поэтому его вызов должен сохраняться до тех пор, пока ваш JavaScript-код не сможет его обработать.

expo-app-intents связывает эти объявления на Swift с JavaScript и сохраняет ожидающие вызовы на уровне нативной платформы. В настоящее время библиотека находится на стадии альфа-тестирования. Ее инициализатор включает шаблоны, которые показывают, как настроить такую связь для ваших собственных действий и данных.

Что может ваше приложение

С помощью expo-app-intents вы можете:

  • Предоставлять действия через Siri и приложение «Быстрые команды», включая фразы, запрашивающие у пользователей недостающие параметры.
  • Позволять пользователям выбирать текущие данные приложения (например, блюдо из меню) в качестве параметра действия.
  • Применять схемы приложений Apple для поддерживаемых действий и контента (например, черновиков писем), чтобы система могла понимать их смысл. Основные домены схем поддерживают обнаружение с помощью Apple Intelligence и Siri.
  • Делать контент приложения доступным для поиска в Spotlight и связывать его с представлением SwiftUI или React Native, которое его отображает, что обеспечивает контекст для экранного интеллекта.

Вы сами выбираете, какие действия и записи предоставить. Шаблоны охватывают счетчик, заказ еды в ресторане и черновики писем. Минимальный вариант создает только модуль настройки.

Как работает expo-app-intents

Во время сборки процессор метаданных App Intents в Xcode извлекает информацию из конкретных типов Swift, которые соответствуют таким протоколам, как AppIntent, AppEntity, EntityQuery и AppShortcutsProvider. JavaScript не может создавать такие объявления во время выполнения, поэтому вам все равно придется самостоятельно писать нативные типы на Swift.

Чтобы упростить добавление этих файлов, expo-app-intents использует встроенные модули Expo (inline modules). Его инициализатор настраивает каталог app-intents/, в котором можно писать файлы Swift, компилируемые в целевой объект приложения без ручных изменений в проекте Xcode. Из этих файлов вы можете импортировать ExpoAppIntents для отправки вызовов и чтения каталогов сущностей, заполняемых с помощью JavaScript.

Это отделяет объявления, необходимые Apple во время сборки, от данных и логики приложения, которые могут изменяться во время выполнения. Изменение объявления Swift или фразы быстрой команды требует новой нативной сборки. Обновление существующего обработчика JavaScript или публикация новых значений каталога — нет.

Установка expo-app-intents

Чтобы установить expo-app-intents в ваш проект, выполните:

Команда инициализации в следующем разделе запишет конфигурацию за вас. Если вы настраиваете все вручную, добавьте плагин конфигурации expo-app-intents и включите встроенные модули для каталога app-intents в вашем файле app.json:

Если вы работаете с агентами программирования, используйте @expo/agent-cli для установки встроенного навыка expo-app-intents. Это позволит агенту добавить поддержку App Intents в ваше приложение, даже если у вас нет опыта нативной разработки:

Начало работы с шаблоном счетчика

Начните с шаблона счетчика, чтобы связать намерение Swift с обработчиком JavaScript и запустить его через Siri или Быстрые команды. Данное руководство ориентировано на iOS, и пакет требует наличия iOS 16.4 или более поздней версии. Вам потребуется сборочный файл для разработки (development build), поскольку пакет недоступен в Expo Go.

Каждый пример заменяет предыдущий. Перед инициализацией следующего примера сохраните все изменения в коде Swift, которые вы хотите оставить, и удалите существующий каталог app-intents/. Также замените компонент предыдущего примера на начальном экране новым. Такой чистый запуск позволяет сохранить руководство простым. Добавление намерения в ваше собственное приложение не требует удаления существующих намерений, а инициализатор сохраняет существующие файлы, чтобы вы могли интегрировать дополнительные примеры вручную.

Запустите инициализатор и выберите шаблон счетчика:

Инициализатор создает каталог app-intents/, добавляет плагин конфигурации и настраивает встроенные модули. Внутри этого каталога файл IncreaseCounterIntent.swift объявляет действие. Вот шаблон, в котором диалоговое окно изменено с «Счетчик увеличен» («Counter increased.») на «Открытие счетчика» («Opening the counter.»):

Параметр openAppWhenRun сообщает системе о необходимости открыть приложение. Функция dispatch() ставит вызов в очередь, не дожидаясь завершения работы JavaScript, поэтому это диалоговое окно описывает открытие приложения, а не изменение состояния на завершенное.

Сгенерированный файл AppShortcuts.swift предоставляет фразу, которая запускает действие:

Конструкция \(.applicationName) подставляет имя вашего приложения. Оставляйте ее в каждой фразе быстрой команды. Сгенерированный файл AppIntentsSetup.swift связывает этот провайдер с пакетом, чтобы он мог запрашивать обновление параметров быстрых команд.

Теперь отобразите этот компонент на своем начальном экране. Смонтируйте обработчик один раз, чтобы несколько компонентов не обрабатывали одни и те же вызовы:

Хук useAppIntents() доставляет ожидающие вызовы при монтировании, а затем обрабатывает вызовы в реальном времени во время работы приложения. Удаление обработанного вызова подтверждает его получение для нативной очереди. Проверка ID предотвращает повторное увеличение счетчика при повторной доставке, пока этот компонент остается смонтированным.

В этом примере значение счетчика хранится в состоянии React, поэтому оно сбрасывается при повторном монтировании компонента. Планирование вызова setCount() не сохраняет изменения персистентно. Для операций, которые должны переживать перезапуск, сохраняйте изменение в приложении и ID обработанного вызова атомарно в собственном хранилище перед подтверждением вызова. Это позволит последующим операциям доставки распознать работу, которая уже была успешно выполнена. Очередь содержит только 100 самых новых вызовов, поэтому очищайте ее по мере обработки действий.

Соберите и запустите приложение после добавления компонента:

После открытия приложения зайдите в приложение «Быстрые команды» на устройстве, найдите ваше приложение и запустите действие Increase Counter. Приложение должно открыться, а значение счетчика — увеличиться на единицу. Вы также можете сказать Siri: «Increase the counter in [имя вашего приложения]». Держите обработчик смонтированным во время повторных вызовов, чтобы видеть, как накапливается значение счетчика.

Передача текущих данных приложения в намерение

У счетчика нет параметров. Шаблон ресторана добавляет обязательную сущность DishEntity, чтобы пользователь мог выбрать блюдо по имени. Чтобы протестировать это, сохраните все изменения в коде Swift, которые вы хотите оставить, удалите каталог app-intents/ из руководства по счетчику и инициализируйте пример ресторана из корневого каталога вашего проекта:

Начало работы с пустого каталога позволяет инициализатору сгенерировать файл AppShortcuts.swift с фразами для ресторана и файл AppIntentsSetup.swift с обработчиком обновления для быстрых команд. Теперь сгенерированные файлы Swift содержат пример ресторана вместо счетчика.

Замените CounterIntentHandler на вашем начальном экране компонентом RestaurantExample, приведенным ниже. При монтировании он публикует небольшое меню и отображает блюдо, выбранное в результате вызова orderFood:

Этот пример подтверждает выбранное блюдо в пользовательском интерфейсе. Заказ при этом не отправляется. Повторная доставка выбирает то же самое блюдо снова. Обработчику производственных заказов требуется постоянная дедупликация, описанная в противоположном примере.

Сгенерированный объект DishQuery считывает этот каталог и преобразует его записи в значения DishEntity. Этот отрывок демонстрирует поиск по идентификатору:

Шаблон также реализует подсказки и сопоставление строк с идентификаторами, названиями и синонимами блюд. Используйте одинаковый тип "dish" в JavaScript и Swift с фиксированными, непустыми ID и непустыми названиями.

Каталоги сохраняются нативно, поэтому запросы могут считывать последнее опубликованное меню до запуска JavaScript. Публикация заменяет каталог и запрашивает обновление параметра быстрого доступа при изменении значений. Публикавайте данные после их загрузки и при каждом изменении меню. Публикация пустого состояния загрузки приведет к очистке предыдущего меню.

Затем OrderFoodIntent.perform() передает выбранное блюдо со следующими данными:

Пересоберите проект с помощью npx expo run:ios --device, откройте приложение и дождитесь сообщения "Menu ready." Скажите Siri: "Order Tiramisu in [название вашего приложения]". Приложение должно отобразить "Selected dish: Tiramisu." Вы также можете сказать "Place an order in [название вашего приложения]" и выбрать блюдо, когда Siri попросит об этом. Эти фразы берутся из сгенерированного файла AppShortcuts.swift.

Описание действий с помощью схемы Apple

Домены схем приложений описывают действия и контент, которыми обмениваются приложения, например создание черновика письма. Соответствие схеме предоставляет системе определенные свойства и поведение для работы. Основные домены Apple поддерживают Apple Intelligence и Siri. Домены, специфичные для быстрых команд, доступны через приложение Shortcuts.

Шаблон электронной почты демонстрирует основной почтовый домен. Его базовые намерения требуют iOS 18 или новее.

Прежде чем выполнить команду ниже, сохраните все изменения в Swift, которые хотите оставить, и удалите существующий каталог app-intents/. Затем инициализируйте полный пример почты, включая необязательную интеграцию со Spotlight и экраном, описанную в следующем разделе:

Связывание с экраном в этом слое требует Xcode 27 или новее и iOS 18.4 или новее. Его отдельное намерение открытия черновика требует iOS 27. Начало работы с чистого листа позволяет инициализатору сгенерировать все файлы Swift и регистрацию сущностей, необходимые для этого примера.

Этот отрывок из CreateDraftIntent.swift демонстрирует аннотацию схемы и ее свойства. Полный сгенерированный файл также импортирует свои зависимости и реализует perform():

Схема определяет обязательные свойства. В полном шаблоне метод perform() создает объект MailDraftEntity, передает createMailDraft в JavaScript и возвращает эту сущность. JavaScript все еще должен сохранить черновик. Пример полезной нагрузки включает количество вложений, а не сами файлы вложений.

Почтовому шаблону не нужны фразы быстрого доступа для обнаружения на основе схем. Соответствие почтовым схемам Apple сообщает системе, какие стандартные действия поддерживает приложение и какие параметры они принимают. Xcode включает эту информацию в метаданные приложения во время сборки, поэтому поддерживаемые функции Siri и Apple Intelligence могут обнаруживать действия без записи AppShortcut. Эти действия также доступны в приложении Shortcuts.

Замените RestaurantExample на вашем начальном экране на MailExample ниже. Эта небольшая демо-версия считывает сохраненные записи черновиков из нативного каталога при запуск обработчика, а затем публикует обновленный список после каждого действия создания или удаления. Ее пользовательский интерфейс отображает те же самые записи. Шаблон сопоставляет название каждой записи с темой, а подзаголовок — с телом письма.

Пример ожидает чтения каталога, прежде чем что-либо изменять, поэтому исходное пустое состояние React никогда не стирает сохраненные записи. Он публикует обновленный каталог перед подтверждением вызовов создания и удаления. Создание черновика с существующим ID заменяет эту запись, а удаление уже отсутствующего ID не приводит ни к каким результатам. Обе операции устойчивы к повторной доставке без добавления дубликатов черновиков.

Для краткости в этой демо-версии в компактном нативном каталоге хранятся только ID, темы и тексты. Полноценное почтовое приложение должно сохранять полные черновики в собственном хранилище данных и публиковать записи, необходимые намерениям. Ветвь openMailDraft выбирает черновик в этом интерфейсе. Это поведение JavaScript, требуемое сгенерированным намерением открытия для iOS 27.

Пересоберите проект с помощью npx expo run:ios --device и запустите приложение. На устройстве, где Siri поддерживает эти действия почтовой схемы, попробуйте следующие запросы, заменив [название вашего приложения] на имя вашего приложения:

  • "Create an email draft in [название вашего приложения] with the subject Release notes and the body The release notes are ready for review."
  • "Delete the email draft titled Release notes in [название вашего приложения]."

Это предлагаемые запросы на естественном языке. Вам не нужно регистрировать их формулировку в качестве фраз быстрого доступа. Первый запрос должен добавить "Release notes" в список и отобразить текст сообщения. Как только запись появится, обработчик также опубликует ее в каталоге, чтобы Siri могла обработать второй запрос. Намерение удаления требует аутентификации и подтверждения на устройстве, а затем передает deleteMailDrafts. Оно не открывает приложение, поэтому, если JavaScript остановлен, откройте приложение позже, чтобы ваш обработчик применил удаление. Черновик должен исчезнуть из списка.

Подключение контента к Spotlight и экрану

Продолжите работу с почтовым примером, сгенерированным выше. Опция --visual-intelligence делает MailDraftEntity индексируемой в Spotlight и добавляет поддержку передачи и открытия черновиков. Она также генерирует AppIntentsSetup.swift с вызовом AppEntityIdentifierRegistry.shared.registerIndexed("mailDraft", as: MailEntity.self) внутри OnCreate. Ручная регистрация не требуется.

Регистрация связывает каталог mailDraft с индексацией Spotlight и делает этот тип доступным для идентификаторов сущностей на экране. Пользовательская индексируемая сущность требует реализации протоколов IndexedEntity и AppIntentEntityRecordConvertible. Сгенерированные расширения предоставляют оба протокола для почтовых черновиков.

MailExample уже публикует черновики, отображаемые им в рамках зарегистрированного типа mailDraft. ID, сгенерированный намерением создания, остается неизменным в каталоге, списке и представлении AppEntityView выбранного черновика. Выбор черновика отображает его тему и текст внутри этого представления, связывая видимый контент с нативной сущностью. Нет необходимости публиковать отдельный набор примеров записей для Spotlight.

Для представления SwiftUI @expo/ui импортируйте { appEntityIdentifier } из "expo-app-intents" и примените appEntityIdentifier('mailDraft', draft.id) через его пропс modifiers. Эта связь сообщает системе, какую сущность представляет данное представление. Соответствующие намерения и запросы определяют, что система может с ними делать.

Используйте register() вместо registerIndexed(), когда вам нужен контекст на экране без индексации Spotlight, управляемой каталогом. Чтобы исключить отдельную запись из этого индекса, сохранив ее доступность для намерений, опубликуйте ее с параметром hideInSpotlight: true.

Чтобы проверить поиск, создайте другой черновик с помощью Siri, указав тему «Spotlight demo». Дождитесь его появления в приложении, а затем выполните поиск по запросу «Spotlight demo» в Spotlight после завершения индексации. В iOS 27 открытие результата черновика должно вызывать openMailDraft. Обработчик выше выбирает соответствующую запись и отображает ее текст. Вы также можете выбрать этот черновик из списка приложения, чтобы отобразить то же представление, связанное с сущностью. Видимость в поиске и ассоциация на экране — это независимые возможности. Аннотация представления предоставляет контекст для поддерживаемых системных действий.

Ограничения

  • expo-app-intents находится в стадии альфа-тестирования, и ее API может измениться.
  • Библиотека недоступна в Expo Go. Используйте сборочный вариант для разработки (development build).
  • Объявления намерений и фразы для быстрых команд находятся в Swift, поэтому их изменение требует новой нативной сборки.
  • Очередь ожидающих вызовов хранит только последние 100 вызовов.
  • Ассоциация на экране требует Xcode 27 или новее и iOS 18.4 или новее. Намерение открытия черновика требует iOS 27.

Попробуйте в своем приложении

Вы можете использовать один из шаблонов инициализации выше в качестве отправной точки или начать с минимального шаблона:

Ознакомьтесь с документацией expo-app-intents для получения полной информации об API, системных требованиях и дополнительных примерах. Исходный код и шаблоны находятся в репозитории Expo, а этот пример приложения демонстрирует более продвинутое использование. Если при использовании шаблонов в своем приложении вы столкнетесь с проблемами, пожалуйста, сообщите о них на GitHub.

О чём эта статья