App Intents та Siri Shortcuts: розширена автоматизація iOS 2026
Повний посібник з App Intents та Siri Shortcuts для iOS 18+. Створення власних дій Siri, інтеграція Apple Intelligence та автоматизація Swift-застосунку у 2026.

У 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.
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<TaskEntity> {
// Створення завдання через сервіс
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 розуміти та маніпулювати даними застосунку.
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 та «Команд».
Використовуйте AppEnum для типів зі сталим набором значень (пріоритет, статус), а AppEntity — для динамічних типів, створюваних користувачем (завдання, нотатки, контакти).
Створення App Enums для сталих значень
App Enums виставляють перерахувальні типи системі, дозволяючи Siri пропонувати контекстні варіанти.
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.
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 App Shortcuts. Варто пріоритизувати найчастіші та найкорисніші для користувачів дії.
Intents зі складними параметрами
Intents можуть приймати складні параметри, включаючи сутності та опції конфігурації.
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 під час голосового виконання, забезпечуючи природний зворотний зв'язок.
Готовий до співбесід з iOS?
Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.
Інтеграція з Apple Intelligence
iOS 18 представляє App Intent Domains — колекції API, спроєктовані для конкретних функцій. Ці домени дозволяють Apple Intelligence розуміти та виконувати дії з більшою точністю.
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 з підтвердженням користувача
Для чутливих дій система може запитати підтвердження перед виконанням.
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+.
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 напряму.
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 представляє макроси @DeferredProperty та @ComputedProperty, що зменшують boilerplate. App Intents також можуть розміщуватися у Swift Packages для повторного використання на різних платформах.
Intents у Swift Packages
App Intents можна визначати у Swift Packages для спільного використання між iOS, macOS та watchOS.
// 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.
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 та оптимальний користувацький досвід.
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 юніт-тестами
- ✅ Локалізувати заголовки та описи
Починай практикувати!
Перевір свої знання з нашими симуляторами співбесід та технічними тестами.
Теги
Поділитися
Пов'язані статті

WidgetKit iOS 17+: Інтерактивні Віджети з App Intents
Повний посібник зі створення інтерактивних iOS-віджетів з WidgetKit та App Intents. Кнопки, перемикачі, анімації та найкращі практики для iOS 17+ у 2026 році.

Combine vs async/await у Swift: Шаблони Прогресивної Міграції
Повний посібник з міграції з Combine на async/await у Swift: прогресивні стратегії, шаблони мостування та співіснування парадигм у iOS-кодових базах.

Питання співбесід з доступності iOS у 2026: VoiceOver і Dynamic Type
Підготовка до співбесід з iOS із ключовими питаннями про доступність: VoiceOver, Dynamic Type, семантичні traits та аудити.