Разработка кастомных Intents для Siri
Кастомные Intents — следующий уровень после базовой интеграции Siri Shortcuts. Вместо простого «открой такой-то экран» пользователь получает полноценный диалог: Siri уточняет параметры, обрабатывает команду в фоне, возвращает результат голосом. Всё это работает без открытия приложения через INExtension.
Структура кастомного Intent
Файл .intentdefinition — центральный элемент. Xcode генерирует из него Swift-код: классы Intent, Response, параметры с типами. Структура параметра:
Parameter: date
Type: Date (INDateComponentsResolutionResult)
Display Name: "дата"
Prompt: "На какое число?"
Siri Dialog: "На какое число назначить встречу?"
Типы параметров: String, Integer, Boolean, Date, CLPlacemark (геолокация), INPerson (контакт из адресной книги), кастомные INObject-типы для сущностей вашего приложения.
Кастомный INObject нужен, когда параметр — это сущность из вашей базы данных (проект, задача, трек):
// Автогенерировано из .intentdefinition
class ProjectObject: INObject {
// identifier и displayString обязательны
}
// В IntentHandler
func provideProjectOptionsCollection(
for intent: CreateTaskIntent,
with completion: @escaping (INObjectCollection<ProjectObject>?, Error?) -> Void
) {
let projects = ProjectRepository().fetchAll()
let objects = projects.map {
ProjectObject(identifier: $0.id, display: $0.name)
}
completion(INObjectCollection(items: objects), nil)
}
Siri покажет список проектов для выбора — либо пользователь произнесёт название, либо Siri уточнит.
Resolve, Confirm, Handle
Жизненный цикл Intent — три метода:
Resolve — валидация и уточнение каждого параметра:
func resolveTaskName(for intent: CreateTaskIntent,
with completion: @escaping (INStringResolutionResult) -> Void) {
guard let name = intent.taskName, name.count >= 2 else {
completion(.needsValue()) // Siri: "Как назвать задачу?"
return
}
guard name.count <= 255 else {
completion(.unsupported(forReason: .tooLong))
return
}
completion(.success(with: name))
}
Confirm — финальная проверка перед выполнением:
func confirm(intent: CreateTaskIntent,
completion: @escaping (CreateTaskIntentResponse) -> Void) {
guard ProjectRepository().exists(id: intent.project?.identifier) else {
let response = CreateTaskIntentResponse(code: .failure, userActivity: nil)
response.failureReason = "Проект не найден"
completion(response)
return
}
completion(CreateTaskIntentResponse(code: .ready, userActivity: nil))
}
Handle — выполнение:
func handle(intent: CreateTaskIntent,
completion: @escaping (CreateTaskIntentResponse) -> Void) {
let store = TaskStore(appGroup: "group.com.yourapp")
let task = store.create(
name: intent.taskName!,
projectId: intent.project?.identifier,
dueDate: intent.dueDate?.dateComponents
)
let response = CreateTaskIntentResponse(code: .success, userActivity: nil)
response.task = TaskObject(identifier: task.id, display: task.name)
completion(response)
}
Response-шаблоны задаются в .intentdefinition: Siri произнесёт «Задача "$(taskName)" создана в проекте "$(project)"» с подстановкой реальных значений.
Интеграция с Shortcuts app
INShortcut + INVoiceShortcutCenter позволяют добавить shortcut прямо из приложения без перехода в Settings:
func addToSiri(intent: CreateTaskIntent) {
let shortcut = INShortcut(intent: intent)
let vc = INUIAddVoiceShortcutViewController(shortcut: shortcut)
vc.delegate = self
present(vc, animated: true)
}
Кнопка «Добавить в Siri» с нативным вью — стандартный паттерн для onboarding.
Интеграция с Widgets через AppIntents (iOS 16+)
С iOS 16 кастомные Intents мигрировали на новый фреймворк AppIntents. Старый SiriKit Intents (.intentdefinition) продолжает работать, но для виджетов конфигурируемых через Shortcuts нужен AppIntent:
struct CreateTaskAppIntent: AppIntent {
static var title: LocalizedStringResource = "Создать задачу"
@Parameter(title: "Название задачи")
var taskName: String
@Parameter(title: "Проект")
var project: ProjectEntity?
func perform() async throws -> some IntentResult & ProvidesDialog {
let task = try await TaskService().create(name: taskName, project: project?.id)
return .result(dialog: "Создана задача \(task.name)")
}
}
AppIntents — это будущее SiriKit на iOS 16+. Меньше boilerplate, поддержка Swift concurrency, работает в виджетах, Spotlight, Shortcuts app.
Типичные проблемы
Extension не вызывается — самая частая проблема. Причины: Intent не добавлен в NSExtension.NSExtensionAttributes.IntentsSupported в Info.plist extension; или Intent добавлен в основной таргет, но не в extension-таргет.
App Group не пробрасывается — Extension и основное приложение работают в разных sandbox. Данные через App Group: FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.com.yourapp") или UserDefaults(suiteName: "group.com.yourapp").
Siri не понимает специфичные термины — INVocabulary.shared().setVocabularyStrings(projectNames, of: .organizationName) и глобальный AppIntentVocabulary.plist для статичных терминов.
Ориентиры по срокам
| Задача | Срок |
|---|---|
| 1 кастомный Intent (простые параметры) | 1–2 дня |
| Intent с кастомными INObject + resolve диалогом | 2–3 дня |
| Миграция на AppIntents (iOS 16+) + widget конфигурация | 3–5 дней |







