# App Intents та Siri Shortcuts: розширена автоматизація iOS 2026 > Повний посібник з App Intents та Siri Shortcuts для iOS 18+. Створення власних дій Siri, інтеграція Apple Intelligence та автоматизація Swift-застосунку у 2026. - Published: 2026-03-08 - Updated: 2026-04-29 - Author: SharpSkill - Tags: app-intents, siri-shortcuts, ios, swift, apple-intelligence - Reading time: 15 min --- У 2026 році з Apple Intelligence та фреймворком App Intents застосунки iOS вступають в епоху, коли намір користувача важливіший за графічний інтерфейс. Застосунки, що не виставляють intents, стають невидимими в орієнтованій на ШІ ОС. App Intents становить основу, яка дає змогу Siri, Spotlight, віджетам та Action Button взаємодіяти з функціями застосунку. > **Що охоплює ця стаття** > > Стаття представляє повне створення App Intents та Siri Shortcuts для iOS 18+, від фундаментальних концепцій до інтеграції з Apple Intelligence та App Intent Domains. ## Зрозуміти фреймворк App Intents Фреймворк App Intents, представлений у iOS 16, модернізує побудову intents у Swift, замінюючи старіший фреймворк SiriKit Intents. Ця декларативна архітектура дає змогу створювати дії, які виявляються системою: Spotlight, застосунок «Команди», Siri та Action Button. ```swift // TaskIntent.swift import AppIntents // AppIntent представляє дію, яку користувач може виконати struct CreateTaskIntent: AppIntent { // Заголовок, що відображається в «Командах» та Siri static var title: LocalizedStringResource = "Створити завдання" // Опис для доступності та підказок static var description = IntentDescription( "Створює нове завдання в застосунку." ) // Параметр з автоматичною валідацією @Parameter(title: "Назва завдання") var taskTitle: String // Необов'язковий параметр зі значенням за замовчуванням @Parameter(title: "Пріоритет", default: .medium) var priority: TaskPriority // Виконання дії func perform() async throws -> some IntentResult & ReturnsValue { // Створення завдання через сервіс let task = TaskService.shared.createTask( title: taskTitle, priority: priority ) // Повертає створену сутність для ланцюгування return .result(value: TaskEntity(task: task)) } } ``` Intent декларує параметри через property wrapper `@Parameter`, дозволяючи Siri запитувати відсутні значення. Метод `perform()` виконує бізнес-логіку та повертає типізований результат. ## Визначення App Entities для даних App Entities представляють «іменники» застосунку: об'єкти, з якими працюють intents. Вони дозволяють Siri розуміти та маніпулювати даними застосунку. ```swift // TaskEntity.swift import AppIntents // Внутрішня модель даних struct Task: Identifiable, Codable { let id: UUID var title: String var priority: TaskPriority var isCompleted: Bool var dueDate: Date? } // Сутність, виставлена системі struct TaskEntity: AppEntity { // Обов'язковий унікальний ідентифікатор var id: UUID // Властивості для відображення var title: String var priority: TaskPriority var isCompleted: Bool // Конфігурація відображення в системі static var typeDisplayRepresentation: TypeDisplayRepresentation = "Завдання" // Візуальне представлення екземпляра var displayRepresentation: DisplayRepresentation { DisplayRepresentation( title: "\(title)", subtitle: "\(priority.rawValue)", image: .init(systemName: isCompleted ? "checkmark.circle.fill" : "circle") ) } // Запит за замовчуванням для пошуку сутностей static var defaultQuery = TaskEntityQuery() // Initializer з внутрішньої моделі init(task: Task) { self.id = task.id self.title = task.title self.priority = task.priority self.isCompleted = task.isCompleted } } // Запит для пошуку та фільтрації сутностей struct TaskEntityQuery: EntityQuery { // Пошук за ідентифікаторами func entities(for identifiers: [UUID]) async throws -> [TaskEntity] { TaskService.shared.fetchTasks() .filter { identifiers.contains($0.id) } .map { TaskEntity(task: $0) } } // Підказки, що відображаються в інтерфейсі func suggestedEntities() async throws -> [TaskEntity] { TaskService.shared.fetchTasks() .filter { !$0.isCompleted } .prefix(5) .map { TaskEntity(task: $0) } } } ``` `EntityQuery` визначає, як система шукає та пропонує сутності. Методи `entities(for:)` та `suggestedEntities()` живлять інтерфейси Siri та «Команд». > **App Enums проти App Entities** > > Використовуйте `AppEnum` для типів зі сталим набором значень (пріоритет, статус), а `AppEntity` — для динамічних типів, створюваних користувачем (завдання, нотатки, контакти). ## Створення App Enums для сталих значень App Enums виставляють перерахувальні типи системі, дозволяючи Siri пропонувати контекстні варіанти. ```swift // TaskPriority.swift import AppIntents // Enum, виставлений системі enum TaskPriority: String, AppEnum, Codable { case low case medium case high // Відображувана назва типу static var typeDisplayRepresentation: TypeDisplayRepresentation = "Пріоритет" // Представлення кожного випадку static var caseDisplayRepresentations: [TaskPriority: DisplayRepresentation] = [ .low: DisplayRepresentation( title: "Низький", image: .init(systemName: "arrow.down.circle") ), .medium: DisplayRepresentation( title: "Середній", image: .init(systemName: "minus.circle") ), .high: DisplayRepresentation( title: "Високий", image: .init(systemName: "exclamationmark.circle") ) ] } // Enum для статусу завдання enum TaskStatus: String, AppEnum, Codable { case pending case inProgress case completed static var typeDisplayRepresentation: TypeDisplayRepresentation = "Статус" static var caseDisplayRepresentations: [TaskStatus: DisplayRepresentation] = [ .pending: "Очікує", .inProgress: "В роботі", .completed: "Завершено" ] } ``` Візуальні представлення (іконки SF Symbols) збагачують відображення в «Командах» та підказках Siri. ## Реалізація AppShortcutsProvider `AppShortcutsProvider` виставляє App Shortcuts системі, роблячи їх миттєво доступними без налаштування користувачем. Ці шорткати з'являються в Spotlight, Siri та на Action Button. ```swift // ShortcutsProvider.swift import AppIntents // Provider, що декларує всі шорткати застосунку struct TaskAppShortcutsProvider: AppShortcutsProvider { // Максимум 10 шорткатів на застосунок @AppShortcutsBuilder static var appShortcuts: [AppShortcut] { // Шорткат для створення завдання AppShortcut( intent: CreateTaskIntent(), phrases: [ // Плейсхолдер .applicationName є ОБОВ'ЯЗКОВИМ "Створити завдання у \(.applicationName)", "Нове завдання в \(.applicationName)", "Додати завдання в \(.applicationName)" ], shortTitle: "Створити завдання", systemImageName: "plus.circle" ) // Шорткат для перегляду завдань AppShortcut( intent: ListTasksIntent(), phrases: [ "Показати мої завдання в \(.applicationName)", "Відобразити завдання \(.applicationName)", "Які мої завдання \(.applicationName)" ], shortTitle: "Мої завдання", systemImageName: "list.bullet" ) // Шорткат із динамічним параметром AppShortcut( intent: CompleteTaskIntent(), phrases: [ "Завершити \(\.$taskName) у \(.applicationName)", "Позначити \(\.$taskName) як виконане у \(.applicationName)" ], shortTitle: "Завершити завдання", systemImageName: "checkmark.circle" ) } } ``` Голосові фрази мають містити плейсхолдер `\(.applicationName)`, щоб Siri ідентифікувала цільовий застосунок. Динамічні параметри на кшталт `\(\.$taskName)` уможливлюють контекстні команди. > **Ліміт у 10 шорткатів** > > Застосунок може декларувати щонайбільше 10 App Shortcuts. Варто пріоритизувати найчастіші та найкорисніші для користувачів дії. ## Intents зі складними параметрами Intents можуть приймати складні параметри, включаючи сутності та опції конфігурації. ```swift // CompleteTaskIntent.swift import AppIntents struct CompleteTaskIntent: AppIntent { static var title: LocalizedStringResource = "Завершити завдання" static var description = IntentDescription( "Позначає завдання як завершене." ) // Параметр сутності з автоматичним пошуком @Parameter(title: "Завдання") var task: TaskEntity // Необов'язковий параметр з датою @Parameter(title: "Дата завершення") var completionDate: Date? // Конфігурація діалогу Siri static var parameterSummary: some ParameterSummary { Summary("Завершити \(\.$task)") { \.$completionDate } } func perform() async throws -> some IntentResult & ProvidesDialog { // Оновлення завдання TaskService.shared.completeTask( id: task.id, completionDate: completionDate ?? Date() ) // Голосовий зворотний зв'язок для Siri return .result( dialog: "Завдання \(task.title) позначено як завершене." ) } } // Intent, що повертає список сутностей struct ListTasksIntent: AppIntent { static var title: LocalizedStringResource = "Перелічити завдання" // Необов'язковий фільтр @Parameter(title: "Статус", default: nil) var statusFilter: TaskStatus? @Parameter(title: "Пріоритет", default: nil) var priorityFilter: TaskPriority? func perform() async throws -> some IntentResult & ReturnsValue<[TaskEntity]> { var tasks = TaskService.shared.fetchTasks() // Застосувати фільтри if let status = statusFilter { tasks = tasks.filter { switch status { case .completed: return $0.isCompleted case .pending, .inProgress: return !$0.isCompleted } } } if let priority = priorityFilter { tasks = tasks.filter { $0.priority == priority } } let entities = tasks.map { TaskEntity(task: $0) } return .result(value: entities) } } ``` `ParameterSummary` визначає, як Siri презентує intent під час голосового виконання, забезпечуючи природний зворотний зв'язок. ## Інтеграція з Apple Intelligence iOS 18 представляє App Intent Domains — колекції API, спроєктовані для конкретних функцій. Ці домени дозволяють Apple Intelligence розуміти та виконувати дії з більшою точністю. ```swift // BookmarkDomainIntents.swift import AppIntents // Відповідність домену Bookmarks для інтеграції Apple Intelligence struct SaveBookmarkIntent: AppIntent { static var title: LocalizedStringResource = "Зберегти закладку" // Параметр URL з автоматичною валідацією @Parameter(title: "URL") var url: URL @Parameter(title: "Заголовок", default: nil) var title: String? @Parameter(title: "Тека", default: nil) var folder: BookmarkFolderEntity? // Відкрити застосунок за потреби static var openAppWhenRun: Bool = false func perform() async throws -> some IntentResult & ProvidesDialog { let bookmark = BookmarkService.shared.save( url: url, title: title, folder: folder?.id ) return .result( dialog: "Закладку збережено: \(bookmark.title)" ) } } // Intent з усвідомленням вмісту екрана (iOS 18.4+) struct AnalyzeScreenContentIntent: AppIntent { static var title: LocalizedStringResource = "Проаналізувати вміст" // Доступ до контексту екрана через Apple Intelligence @Parameter(title: "Контекст") var screenContext: String? func perform() async throws -> some IntentResult & ProvidesDialog { guard let context = screenContext else { return .result(dialog: "Немає вмісту для аналізу.") } // Обробити вміст, витягнутий Apple Intelligence let analysis = ContentAnalyzer.analyze(context) return .result(dialog: analysis.summary) } } ``` Заздалегідь визначені домени (Books, Camera, Spreadsheets) дозволяють Siri точно відповідати на запити завдяки моделям, навченим на цих специфічних завданнях. ## Дії Siri з підтвердженням користувача Для чутливих дій система може запитати підтвердження перед виконанням. ```swift // DeleteTaskIntent.swift import AppIntents struct DeleteTaskIntent: AppIntent { static var title: LocalizedStringResource = "Видалити завдання" @Parameter(title: "Завдання") var task: TaskEntity // Потребує підтвердження користувача static var isDiscoverable: Bool = true func perform() async throws -> some IntentResult & ProvidesDialog { // Запит підтвердження через діалог try await requestConfirmation( result: .result( dialog: "Ви впевнені, що бажаєте видалити \(task.title)?" ) ) // Видалення після підтвердження TaskService.shared.deleteTask(id: task.id) return .result( dialog: "Завдання \(task.title) видалено." ) } } // Intent з кількома кроками діалогу struct ScheduleTaskIntent: AppIntent { static var title: LocalizedStringResource = "Запланувати завдання" @Parameter(title: "Завдання") var task: TaskEntity @Parameter(title: "Дата") var scheduledDate: Date @Parameter(title: "Нагадування", default: true) var setReminder: Bool static var parameterSummary: some ParameterSummary { When(\.$setReminder, .equalTo, true) { Summary("Запланувати \(\.$task) на \(\.$scheduledDate) з нагадуванням") } otherwise: { Summary("Запланувати \(\.$task) на \(\.$scheduledDate)") } } func perform() async throws -> some IntentResult & ProvidesDialog { TaskService.shared.schedule( taskId: task.id, date: scheduledDate, reminder: setReminder ) let dateFormatter = DateFormatter() dateFormatter.dateStyle = .medium dateFormatter.timeStyle = .short let formattedDate = dateFormatter.string(from: scheduledDate) return .result( dialog: "Завдання заплановано на \(formattedDate)." ) } } ``` Метод `requestConfirmation` призупиняє виконання до валідації користувачем, захищаючи від випадкових дій. ## Intents в інтерактивних віджетах App Intents природно інтегруються з WidgetKit для створення інтерактивних віджетів у iOS 17+. ```swift // TaskWidgetIntents.swift import AppIntents import WidgetKit // Intent, оптимізований для віджета (швидке виконання) struct ToggleTaskFromWidgetIntent: AppIntent { static var title: LocalizedStringResource = "Перемкнути завдання" @Parameter(title: "ID завдання") var taskID: String init() {} init(taskID: UUID) { self.taskID = taskID.uuidString } // Без діалогу для віджетів func perform() async throws -> some IntentResult { guard let uuid = UUID(uuidString: taskID) else { return .result() } TaskService.shared.toggleCompletion(taskId: uuid) // Миттєве оновлення віджета WidgetCenter.shared.reloadTimelines(ofKind: "TaskWidget") return .result() } } // View віджета з інтерактивною кнопкою import SwiftUI struct TaskWidgetView: View { let task: Task var body: some View { Button(intent: ToggleTaskFromWidgetIntent(taskID: task.id)) { HStack { Image(systemName: task.isCompleted ? "checkmark.circle.fill" : "circle") .foregroundStyle(task.isCompleted ? .green : .secondary) Text(task.title) .strikethrough(task.isCompleted) } .padding() } .buttonStyle(.plain) } } ``` Віджети використовують синтаксис `Button(intent:)` для прямого з'єднання взаємодії з App Intent без відкриття застосунку. ## Налаштування Action Button Action Button на iPhone 15 Pro і пізніших може запускати App Shortcuts напряму. ```swift // ActionButtonIntent.swift import AppIntents // Intent, оптимізований для Action Button struct QuickCaptureIntent: AppIntent { static var title: LocalizedStringResource = "Швидке захоплення" static var description = IntentDescription( "Швидко створює завдання з заголовком." ) // Відкриває застосунок для введення static var openAppWhenRun: Bool = true func perform() async throws -> some IntentResult & OpensIntent { // Сповіщення для відкриття екрана швидкого захоплення NotificationCenter.default.post( name: .quickCaptureTriggered, object: nil ) return .result(opensIntent: ShowQuickCaptureViewIntent()) } } // Декларація в AppShortcutsProvider для Action Button extension TaskAppShortcutsProvider { @AppShortcutsBuilder static var appShortcuts: [AppShortcut] { // ... інші шорткати AppShortcut( intent: QuickCaptureIntent(), phrases: [ "Швидке захоплення \(.applicationName)", "Швидка нотатка \(.applicationName)" ], shortTitle: "Захопити", systemImageName: "bolt.circle" ) } } ``` Користувачі можуть налаштувати Action Button для запуску цього шорткату через Налаштування > Action Button. > **Макроси iOS 18+** > > iOS 18 представляє макроси `@DeferredProperty` та `@ComputedProperty`, що зменшують boilerplate. App Intents також можуть розміщуватися у Swift Packages для повторного використання на різних платформах. ## Intents у Swift Packages App Intents можна визначати у Swift Packages для спільного використання між iOS, macOS та watchOS. ```swift // Package.swift // swift-tools-version: 5.9 import PackageDescription let package = Package( name: "TaskIntents", platforms: [ .iOS(.v17), .macOS(.v14), .watchOS(.v10) ], products: [ .library(name: "TaskIntents", targets: ["TaskIntents"]) ], dependencies: [], targets: [ .target( name: "TaskIntents", dependencies: [] ) ] ) // Sources/TaskIntents/SharedIntents.swift import AppIntents // Спільний крос-платформений intent public struct SharedCreateTaskIntent: AppIntent { public static var title: LocalizedStringResource = "Створити завдання" @Parameter(title: "Заголовок") public var taskTitle: String public init() {} public func perform() async throws -> some IntentResult { // Спільна логіка await TaskRepository.shared.create(title: taskTitle) return .result() } } // Платформо-специфічне розширення в застосунку #if os(iOS) extension SharedCreateTaskIntent { // Поведінка, специфічна для iOS static var openAppWhenRun: Bool = false } #endif ``` Ця архітектура зберігає єдину кодову базу для intents, адаптуючи поведінку до платформи. ## Тестування App Intents App Intents можна юніт-тестувати, як і будь-який код Swift. ```swift // TaskIntentTests.swift import XCTest import AppIntents @testable import TaskApp final class TaskIntentTests: XCTestCase { override func setUp() { super.setUp() // Скинути сервіс для ізольованих тестів TaskService.shared.reset() } func testCreateTaskIntent() async throws { // Given var intent = CreateTaskIntent() intent.taskTitle = "Тестове завдання" intent.priority = .high // When let result = try await intent.perform() // Then let tasks = TaskService.shared.fetchTasks() XCTAssertEqual(tasks.count, 1) XCTAssertEqual(tasks.first?.title, "Тестове завдання") XCTAssertEqual(tasks.first?.priority, .high) } func testCompleteTaskIntent() async throws { // Given let task = TaskService.shared.createTask( title: "Завдання для завершення", priority: .medium ) var intent = CompleteTaskIntent() intent.task = TaskEntity(task: task) // When _ = try await intent.perform() // Then let updatedTask = TaskService.shared.fetchTask(id: task.id) XCTAssertTrue(updatedTask?.isCompleted ?? false) } func testEntityQuery() async throws { // Given let task1 = TaskService.shared.createTask(title: "Завдання 1", priority: .low) let task2 = TaskService.shared.createTask(title: "Завдання 2", priority: .high) let query = TaskEntityQuery() // When let entities = try await query.entities(for: [task1.id, task2.id]) // Then XCTAssertEqual(entities.count, 2) } } ``` Тести перевіряють поведінку intents незалежно від системного інтерфейсу. ## Найкращі практики та оптимізації Декілька патернів забезпечують продуктивні App Intents та оптимальний користувацький досвід. ```swift // BestPractices.swift import AppIntents // 1. Локалізовані фрази struct LocalizedTaskIntent: AppIntent { static var title: LocalizedStringResource = "task.create.title" static var description = IntentDescription( "task.create.description", categoryName: "task.category" ) @Parameter(title: "task.parameter.title") var taskTitle: String func perform() async throws -> some IntentResult { // ... return .result() } } // 2. Належна обробка помилок enum TaskIntentError: Error, CustomLocalizedStringResourceConvertible { case taskNotFound case invalidInput case serviceUnavailable var localizedStringResource: LocalizedStringResource { switch self { case .taskNotFound: return "Завдання не знайдено." case .invalidInput: return "Некоректні дані." case .serviceUnavailable: return "Сервіс тимчасово недоступний." } } } struct RobustTaskIntent: AppIntent { static var title: LocalizedStringResource = "Надійний intent" @Parameter(title: "ID") var taskId: String func perform() async throws -> some IntentResult & ProvidesDialog { guard let uuid = UUID(uuidString: taskId) else { throw TaskIntentError.invalidInput } guard let task = TaskService.shared.fetchTask(id: uuid) else { throw TaskIntentError.taskNotFound } return .result(dialog: "Завдання знайдено: \(task.title)") } } // 3. Оптимізація EntityQuery struct OptimizedTaskQuery: EntityStringQuery { // Оптимізований текстовий пошук func entities(matching string: String) async throws -> [TaskEntity] { // Пошук на боці сервісу з лімітом TaskService.shared.search(query: string, limit: 10) .map { TaskEntity(task: $0) } } // Обмежені підказки задля продуктивності func suggestedEntities() async throws -> [TaskEntity] { TaskService.shared.fetchRecentTasks(limit: 5) .map { TaskEntity(task: $0) } } } // 4. Focus Filter для режимів Зосередження struct TaskFocusFilter: SetFocusFilterIntent { static var title: LocalizedStringResource = "Фільтрувати завдання" @Parameter(title: "Показати лише високий пріоритет") var showHighPriorityOnly: Bool func perform() async throws -> some IntentResult { TaskService.shared.setFocusFilter(highPriorityOnly: showHighPriorityOnly) return .result() } } ``` Ці патерни забезпечують плавну інтеграцію з системою зі збереженням оптимальної продуктивності. ## Висновок App Intents та Siri Shortcuts змінюють спосіб взаємодії користувачів із застосунками iOS. У 2026 році з Apple Intelligence виставляння intents більше не є опційним — це необхідно для надання сучасного та інтуїтивного досвіду. ### Чек-лист App Intents iOS 18+ - ✅ Створити AppIntents для основних дій застосунку - ✅ Визначити AppEntities для маніпульованих даних - ✅ Використовувати AppEnum для перерахувальних типів - ✅ Реалізувати AppShortcutsProvider з голосовими фразами - ✅ Дотриматися ліміту в 10 App Shortcuts - ✅ Включити `\(.applicationName)` у всі фрази - ✅ Налаштувати ParameterSummary для зворотного зв'язку Siri - ✅ Інтегрувати домени Apple Intelligence, де це доречно - ✅ Тестувати intents юніт-тестами - ✅ Локалізувати заголовки та описи --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/uk/blog/ios/app-intents-siri-shortcuts-advanced-ios-automation