App Intents 2.0 та Siri Shortcuts: посібник з автоматизації iOS 27

Повний посібник з App Intents 2.0 та Siri Shortcuts для iOS 27. Створення streaming responses, багатотурових розмов, View Annotations та інтеграція з Foundation Models.

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

App Intents 2.0 та Siri Shortcuts є єдиним шляхом для сторонніх застосунків інтегруватися з Siri на iOS 27. Після WWDC 2026 Apple оголосила SiriKit застарілим та позиціонувала App Intents як обов'язковий фреймворк для голосової взаємодії, виявлення в Spotlight та автоматизації.

Що охоплює ця стаття

Стаття представляє повне створення App Intents та Siri Shortcuts для iOS 27, від фундаментальних концепцій до streaming responses, багатотурових розмов та View Annotations API.

Розуміння фреймворку App Intents 2.0

Фреймворк App Intents, представлений в iOS 16 та розширений до версії 2.0 на WWDC 2026, є Swift-native декларативним фреймворком для побудови дій, що виявляються системою. iOS 27 приносить чотири основні нововведення: streaming responses для тривалих операцій, багатотурові follow-up'и розмов, View Annotations для посилань на екранні елементи та App Schemas для семантичного розуміння без тренувальних фраз.

На WWDC 2026 Apple офіційно оголосила SiriKit застарілим і зробила App Intents єдиним способом взаємодії Siri зі сторонніми застосунками. Існуючий код SiriKit продовжує компілюватися з попередженнями про застарілість, але Apple сигналізує про дво-трирічне вікно підтримки перед видаленням.

TaskIntent.swiftswift
import AppIntents

// An AppIntent represents an action users can perform
struct CreateTaskIntent: AppIntent {
    // Title displayed in Shortcuts and Siri
    static var title: LocalizedStringResource = "Create a task"

    // Description for accessibility and suggestions
    static var description = IntentDescription(
        "Creates a new task in the application."
    )

    // Parameter with automatic validation
    @Parameter(title: "Task title")
    var taskTitle: String

    // Optional parameter with default value
    @Parameter(title: "Priority", default: .medium)
    var priority: TaskPriority

    // Action execution with async/await
    func perform() async throws -> some IntentResult & ReturnsValue<TaskEntity> {
        // Create task via service
        let task = TaskService.shared.createTask(
            title: taskTitle,
            priority: priority
        )

        // Return created entity for chaining
        return .result(value: TaskEntity(task: task))
    }
}

Intent декларує параметри через property wrapper @Parameter, дозволяючи Siri запитувати відсутні значення в режимі розмови. Метод perform() виконує бізнес-логіку та повертає типізований результат. Сама система типів слугує схемою, яку Siri AI динамічно виявляє.

Streaming Responses для тривалих дій

App Intents 2.0 представляє streaming responses, що дозволяють intent'ам повідомляти про прогрес під час виконання замість блокування до завершення. Ця можливість адресує сценарії на кшталт завантаження файлів, синхронізації даних чи складних обчислень, де користувачам потрібен зворотний зв'язок.

StreamingExportIntent.swiftswift
import AppIntents

struct ExportDataIntent: AppIntent {
    static var title: LocalizedStringResource = "Export data"

    @Parameter(title: "Format")
    var format: ExportFormat

    // Streaming response for progress reporting
    func perform() async throws -> some IntentResult & ProvidesDialog {
        let totalItems = DataService.shared.itemCount
        var processed = 0

        // Stream progress updates to Siri
        for item in DataService.shared.allItems {
            await exportItem(item, format: format)
            processed += 1

            // Report progress at intervals
            if processed % 100 == 0 {
                await reportProgress(
                    "Exported (processed) of (totalItems) items..."
                )
            }
        }

        return .result(
            dialog: "Export complete. (totalItems) items saved as (format.rawValue)."
        )
    }
}

Streaming responses забезпечують природний зворотний зв'язок під час операцій, що тривають довше кількох секунд, підтримуючи залученість користувача замість залишати його в тиші.

Багатотурові follow-up'и розмов

Багатотурові розмови дозволяють Siri ставити уточнювальні запитання та продовжувати взаємодію в рамках однієї сесії. Ця функція трансформує App Intents з одноразових команд у розмовні робочі процеси.

ConversationalTaskIntent.swiftswift
import AppIntents

struct SmartTaskIntent: AppIntent {
    static var title: LocalizedStringResource = "Create smart task"

    @Parameter(title: "Task title")
    var taskTitle: String?

    @Parameter(title: "Priority")
    var priority: TaskPriority?

    @Parameter(title: "Due date")
    var dueDate: Date?

    // Multi-turn conversation flow
    func perform() async throws -> some IntentResult & ProvidesDialog {
        // Request missing parameters conversationally
        let title = try await taskTitle ?? requestValue(
            for: .,
            dialog: "What should the task be called?"
        )

        let taskPriority = try await priority ?? requestValue(
            for: .,
            dialog: "What priority level?"
        )

        // Optional follow-up
        let date: Date?
        if try await requestConfirmation(
            result: .result(dialog: "Should this task have a due date?")
        ) {
            date = try await requestValue(
                for: .,
                dialog: "When is it due?"
            )
        } else {
            date = nil
        }

        let task = TaskService.shared.createTask(
            title: title,
            priority: taskPriority,
            dueDate: date
        )

        return .result(
            dialog: "Created '(task.title)' with (taskPriority.rawValue) priority."
        )
    }
}

Метод requestValue(for:dialog:) призупиняє виконання, поки Siri не отримає відповідь користувача, створюючи природний обмін замість вимагання всіх параметрів наперед.

View Annotations API для екранних посилань

View Annotations API, новинка iOS 27, дозволяє користувачам посилатися на UI-елементи безпосередньо в командах Siri, використовуючи фрази на кшталт "це фото", "третє" або "те повідомлення". Ця можливість вимагає анотування SwiftUI-в'ю семантичною інформацією.

AnnotatedPhotoView.swiftswift
import SwiftUI
import AppIntents

struct PhotoGridView: View {
    let photos: [Photo]

    var body: some View {
        LazyVGrid(columns: [GridItem(.adaptive(minimum: 100))]) {
            ForEach(photos) { photo in
                PhotoThumbnail(photo: photo)
                    // Annotate view for Siri reference
                    .appIntentAnnotation(
                        entity: PhotoEntity(photo: photo),
                        label: photo.title
                    )
            }
        }
    }
}

// Intent that accepts view-referenced entities
struct SharePhotoIntent: AppIntent {
    static var title: LocalizedStringResource = "Share photo"

    // Parameter resolved from on-screen annotation
    @Parameter(title: "Photo", supportsViewAnnotation: true)
    var photo: PhotoEntity

    func perform() async throws -> some IntentResult & ProvidesDialog {
        await ShareService.share(photo.id)
        return .result(dialog: "Photo shared.")
    }
}

Коли користувач каже "поділитися цим фото" під час перегляду сітки, Siri розв'язує "це" на поточно сфокусований або останній торкнутий PhotoEntity через анотацію. Це усуває необхідність словесного опису елементів.

Сумісність пристроїв

View Annotations потребують iOS 27, але сам шар App Intents працює на кожному пристрої з iOS 27, включаючи iPhone 11 та новіші. Функції Apple Intelligence на кшталт усвідомлення екрану потребують iPhone 15 Pro або новішого.

Визначення App Entities для даних

App Entities представляють "іменники" застосунку: об'єкти, з якими працюють intent'и. Вони дозволяють Siri розуміти, шукати та маніпулювати даними застосунку. Побудова ефективних сутностей є важливою для патернів управління станом SwiftUI у застосунках, орієнтованих на intent'и.

TaskEntity.swiftswift
import AppIntents

// Internal data model
struct Task: Identifiable, Codable {
    let id: UUID
    var title: String
    var priority: TaskPriority
    var isCompleted: Bool
    var dueDate: Date?
}

// Entity exposed to the system
struct TaskEntity: AppEntity {
    // Required unique identifier
    var id: UUID

    // Displayable properties
    var title: String
    var priority: TaskPriority
    var isCompleted: Bool

    // Display configuration in the system
    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Task"

    // Visual representation of the instance
    var displayRepresentation: DisplayRepresentation {
        DisplayRepresentation(
            title: "(title)",
            subtitle: "(priority.rawValue)",
            image: .init(systemName: isCompleted ? "checkmark.circle.fill" : "circle")
        )
    }

    // Default query for searching entities
    static var defaultQuery = TaskEntityQuery()

    // Initializer from internal model
    init(task: Task) {
        self.id = task.id
        self.title = task.title
        self.priority = task.priority
        self.isCompleted = task.isCompleted
    }
}

// Optimized entity query with search
struct TaskEntityQuery: EntityStringQuery {
    // Text search with service-side filtering
    func entities(matching string: String) async throws -> [TaskEntity] {
        TaskService.shared.search(query: string, limit: 10)
            .map { TaskEntity(task: /bin/bash) }
    }

    // Search by identifiers
    func entities(for identifiers: [UUID]) async throws -> [TaskEntity] {
        TaskService.shared.fetchTasks()
            .filter { identifiers.contains(/bin/bash.id) }
            .map { TaskEntity(task: /bin/bash) }
    }

    // Limited suggestions for performance
    func suggestedEntities() async throws -> [TaskEntity] {
        TaskService.shared.fetchRecentTasks(limit: 5)
            .map { TaskEntity(task: /bin/bash) }
    }
}

Протокол EntityStringQuery додає можливість текстового пошуку, дозволяючи Siri знаходити сутності за назвою. Метод suggestedEntities() живить інтерфейси Siri та «Команд» релевантними опціями.

AppEnum для сталих значень

Використовуйте AppEnum для типів зі сталим набором значень (пріоритет, статус), а AppEntity для динамічних типів, створюваних користувачем (завдання, нотатки, контакти). Змішування їх спричиняє зайву складність.

Створення App Enums для сталих значень

App Enums виставляють перерахувальні типи системі, дозволяючи Siri пропонувати контекстні варіанти з візуальними представленнями.

TaskPriority.swiftswift
import AppIntents

// Enum exposed to the system
enum TaskPriority: String, AppEnum, Codable {
    case low
    case medium
    case high

    // Type name displayed
    static var typeDisplayRepresentation: TypeDisplayRepresentation = "Priority"

    // Representation of each case
    static var caseDisplayRepresentations: [TaskPriority: DisplayRepresentation] = [
        .low: DisplayRepresentation(
            title: "Low",
            image: .init(systemName: "arrow.down.circle")
        ),
        .medium: DisplayRepresentation(
            title: "Medium",
            image: .init(systemName: "minus.circle")
        ),
        .high: DisplayRepresentation(
            title: "High",
            image: .init(systemName: "exclamationmark.circle")
        )
    ]
}

Іконки SF Symbols збагачують відображення в «Командах» та підказках Siri, пришвидшуючи вибір порівняно зі словесним описом.

Готовий до співбесід з iOS?

Практикуйся з нашими інтерактивними симуляторами, flashcards та технічними тестами.

Реалізація AppShortcutsProvider

AppShortcutsProvider виставляє App Shortcuts системі, роблячи їх миттєво доступними без налаштування користувачем. Ці шорткати з'являються в Spotlight, Siri, на Action Button та в нових агентних потоках Siri AI.

ShortcutsProvider.swiftswift
import AppIntents

// Provider declaring all app shortcuts
struct TaskAppShortcutsProvider: AppShortcutsProvider {
    // Maximum 10 shortcuts per application
    @AppShortcutsBuilder
    static var appShortcuts: [AppShortcut] {
        // Shortcut to create a task
        AppShortcut(
            intent: CreateTaskIntent(),
            phrases: [
                // The .applicationName placeholder is REQUIRED
                "Create a task with (.applicationName)",
                "New task in (.applicationName)",
                "Add a task to (.applicationName)"
            ],
            shortTitle: "Create Task",
            systemImageName: "plus.circle"
        )

        // Shortcut with dynamic parameter
        AppShortcut(
            intent: CompleteTaskIntent(),
            phrases: [
                "Complete (.) in (.applicationName)",
                "Mark (.) as done with (.applicationName)"
            ],
            shortTitle: "Complete Task",
            systemImageName: "checkmark.circle"
        )

        // Shortcut for smart conversational intent
        AppShortcut(
            intent: SmartTaskIntent(),
            phrases: [
                "Quick task (.applicationName)",
                "Remind me (.applicationName)"
            ],
            shortTitle: "Smart Task",
            systemImageName: "brain"
        )
    }
}

Голосові фрази мають містити плейсхолдер (.applicationName), щоб Siri ідентифікувала цільовий застосунок. Динамічні параметри на кшталт (.) уможливлюють контекстні команди.

Intent'и в інтерактивних віджетах

App Intents інтегруються з WidgetKit для створення інтерактивних віджетів. Розуміння патернів навігації SwiftUI допомагає при побудові переходів віджет-застосунок.

TaskWidgetIntents.swiftswift
import AppIntents
import WidgetKit

// Widget-optimized intent (fast execution)
struct ToggleTaskFromWidgetIntent: AppIntent {
    static var title: LocalizedStringResource = "Toggle task"

    @Parameter(title: "Task ID")
    var taskID: String

    init() {}

    init(taskID: UUID) {
        self.taskID = taskID.uuidString
    }

    // No dialog for widgets
    func perform() async throws -> some IntentResult {
        guard let uuid = UUID(uuidString: taskID) else {
            return .result()
        }

        TaskService.shared.toggleCompletion(taskId: uuid)

        // Immediate widget refresh
        WidgetCenter.shared.reloadTimelines(ofKind: "TaskWidget")

        return .result()
    }
}

// Widget view with interactive button
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 без відкриття застосунку.

Інтеграція з Foundation Models Framework

iOS 27 представляє Foundation Models framework, що уможливлює on-device LLM-інференс. Хоча Foundation Models не має прямої інтеграції з Siri, застосунки можуть використовувати його всередині App Intents для обробки природної мови чи генерації відповідей.

AIAssistedIntent.swiftswift
import AppIntents
import FoundationModels

struct SummarizeNotesIntent: AppIntent {
    static var title: LocalizedStringResource = "Summarize notes"

    @Parameter(title: "Topic")
    var topic: String

    func perform() async throws -> some IntentResult & ProvidesDialog {
        let notes = NoteService.shared.fetchNotes(matching: topic)
        let content = notes.map(.content).joined(separator: "

")

        // Use Foundation Models for on-device summarization
        let model = LanguageModel.default
        let summary = try await model.generate(
            prompt: "Summarize these notes in 2 sentences: (content)",
            maxTokens: 100
        )

        return .result(dialog: IntentDialog(summary))
    }
}

Foundation Models працює зі швидкістю приблизно 30 токенів на секунду на пристрої без жодних витрат за запит. Фреймворк потребує iPhone 15 Pro або iPhone 16 та новіших для on-device обробки.

Міграція з SiriKit до App Intents

Для застосунків, що досі використовують SiriKit, міграція відбувається за структурованим процесом. Xcode 27 надає інструмент "Convert to App Intent" для конфігурацій віджетів, але кастомні обробники потребують ручного переписування.

swift
// Before: SiriKit Intent Handler
class CreateTaskIntentHandler: NSObject, CreateTaskIntentHandling {
    func handle(intent: CreateTaskIntent) async -> CreateTaskIntentResponse {
        guard let title = intent.taskTitle else {
            return CreateTaskIntentResponse(code: .failure, userActivity: nil)
        }

        let task = TaskService.shared.createTask(title: title)
        return CreateTaskIntentResponse.success(task: task)
    }
}

// After: App Intent (iOS 27)
struct CreateTaskIntent: AppIntent {
    static var title: LocalizedStringResource = "Create a task"

    @Parameter(title: "Task title")
    var taskTitle: String

    func perform() async throws -> some IntentResult & ReturnsValue<TaskEntity> {
        let task = TaskService.shared.createTask(title: taskTitle)
        return .result(value: TaskEntity(task: task))
    }
}

Міграція усуває файли .intentdefinition та таргети Intent Extension. Уся логіка intent'ів переміщується до головного таргету застосунку як Swift-структури.

Тестування App Intents

Фреймворк AppIntentsTesting валідує intent'и через реальну інфраструктуру Siri, «Команд» та Spotlight без моків.

TaskIntentTests.swiftswift
import XCTest
import AppIntentsTesting
@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 = "Test Task"
        intent.priority = .high

        // When
        let result = try await intent.perform()

        // Then
        let tasks = TaskService.shared.fetchTasks()
        XCTAssertEqual(tasks.count, 1)
        XCTAssertEqual(tasks.first?.title, "Test Task")
        XCTAssertEqual(tasks.first?.priority, .high)
    }

    func testEntityQuery() async throws {
        // Given
        let task1 = TaskService.shared.createTask(title: "Task 1", priority: .low)
        let task2 = TaskService.shared.createTask(title: "Task 2", priority: .high)

        let query = TaskEntityQuery()

        // When
        let entities = try await query.entities(for: [task1.id, task2.id])

        // Then
        XCTAssertEqual(entities.count, 2)
    }

    func testStreamingIntent() async throws {
        // Given
        var intent = ExportDataIntent()
        intent.format = .json

        // When
        let result = try await intent.perform()

        // Then: verify completion dialog
        XCTAssertNotNil(result)
    }
}

Тести верифікують поведінку intent'ів незалежно від системного інтерфейсу, виявляючи регресії до деплою.

Джерела

Чек-лист імплементації App Intents iOS 27

Для застосунків, що таргетують iOS 27, App Intents більше не є опційними. Siri AI компонує багатокрокові дії між застосунками, і застосунки без опублікованих intent'ів виключаються з цих агентних потоків.

  • Створіть AppIntents для основних дій застосунку
  • Визначте AppEntities для маніпульованих даних
  • Використовуйте AppEnum для перерахувальних типів
  • Реалізуйте AppShortcutsProvider з голосовими фразами
  • Дотримуйтесь ліміту в 10 App Shortcuts
  • Включіть (.applicationName) у всі фрази
  • Додайте View Annotations до SwiftUI-в'ю для екранних посилань
  • Реалізуйте streaming responses для тривалих операцій
  • Підтримуйте багатотурові розмови для складних потоків
  • Мігруйте з SiriKit до закриття вікна підтримки
  • Тестуйте intent'и з фреймворком AppIntentsTesting
  • Локалізуйте заголовки та описи

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

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

Щоденний виклик

Чи знайдеш ти помилку в iOS?

Справжній фрагмент коду, прихована помилка, одна спроба на день. Щоб спробувати, акаунт не потрібен.

Anthony Fillion-Maillet

Автор:

Anthony Fillion-Maillet

Засновник SharpSkill

Fullstack-розробник понад 10 років. Керує SharpSkill і відповідає за все, що тут публікується.

Оновлено 20 серпня 2026 р.

Теги

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

Поділитися

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