App Intents та Siri Shortcuts: розширена автоматизація iOS 2026

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

App Intents та Siri Shortcuts для розширеної автоматизації iOS зі Swift та Apple Intelligence

У 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.

TaskIntent.swiftswift
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 розуміти та маніпулювати даними застосунку.

TaskEntity.swiftswift
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 пропонувати контекстні варіанти.

TaskPriority.swiftswift
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.

ShortcutsProvider.swiftswift
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 можуть приймати складні параметри, включаючи сутності та опції конфігурації.

CompleteTaskIntent.swiftswift
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 розуміти та виконувати дії з більшою точністю.

BookmarkDomainIntents.swiftswift
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 з підтвердженням користувача

Для чутливих дій система може запитати підтвердження перед виконанням.

DeleteTaskIntent.swiftswift
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+.

TaskWidgetIntents.swiftswift
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 напряму.

ActionButtonIntent.swiftswift
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.

Package.swiftswift
// 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.

TaskIntentTests.swiftswift
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 та оптимальний користувацький досвід.

BestPractices.swiftswift
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 юніт-тестами
  • ✅ Локалізувати заголовки та описи

Починай практикувати!

Перевір свої знання з нашими симуляторами співбесід та технічними тестами.

Теги

#app-intents
#siri-shortcuts
#ios
#swift
#apple-intelligence

Поділитися

Пов'язані статті