App Intents 2.0とSiri Shortcuts: iOS 27自動化ガイド

iOS 27向けApp Intents 2.0とSiri Shortcutsの完全ガイドです。ストリーミングレスポンス、マルチターン会話、View Annotationsの構築、Foundation Modelsとの統合を解説します。

SwiftとApple Intelligenceによる高度なiOS自動化のためのApp IntentsとSiri Shortcuts

App Intents 2.0とSiri Shortcutsは、iOS 27でサードパーティアプリがSiriと統合するための唯一の方法です。WWDC 2026で、AppleはSiriKitを非推奨とし、App Intentsを音声インタラクション、Spotlight検出、自動化ワークフローの必須フレームワークとして位置づけました。

本記事の内容

本記事では、iOS 27向けのApp IntentsとSiri Shortcutsの完全な作成方法を、基本概念からストリーミングレスポンス、マルチターン会話、View Annotations APIまで紹介します。

App Intents 2.0フレームワークを理解する

iOS 16で最初に導入され、WWDC 2026でバージョン2.0に拡張されたApp Intentsフレームワークは、システムが発見可能なアクションを構築するためのSwiftネイティブな宣言的フレームワークです。iOS 27では4つの主要な追加機能があります。長時間操作のためのストリーミングレスポンス、マルチターン会話のフォローアップ、画面上の要素を参照するためのView Annotations、トレーニングフレーズなしでセマンティックな理解を可能にするApp Schemasです。

WWDC 2026で、Appleは正式にSiriKitを非推奨とし、App IntentsをSiriがサードパーティアプリと対話する唯一の方法としました。既存のSiriKitコードは非推奨の警告付きでコンパイルが継続されますが、Appleは削除までの2〜3年のサポート期間を示しています。

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は@Parameterプロパティラッパーでパラメータを宣言し、Siriが不足している値を会話的に要求できるようにします。perform()メソッドはビジネスロジックを実行し、型付けされた結果を返します。型システム自体が、Siri AIが動的に発見するスキーマとして機能します。

長時間実行アクションのためのストリーミングレスポンス

App Intents 2.0はストリーミングレスポンスを導入し、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)."
        )
    }
}

ストリーミングレスポンスにより、数秒以上かかる操作中に自然なフィードバックが可能となり、ユーザーを無音で待たせることなくエンゲージメントを維持します。

マルチターン会話フォローアップ

マルチターン会話により、Siriは1つのセッション内で明確化の質問をし、対話を継続できます。この機能により、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で新しく導入され、ユーザーが「この写真」「3番目のもの」「あのメッセージ」といったフレーズでSiriコマンド内のUI要素を直接参照できるようにします。この機能には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レイヤー自体はiPhone 11以降を含むすべてのiOS 27デバイスで動作します。画面認識などのApple Intelligence機能にはiPhone 15 Pro以降が必要です。

データのためのApp Entitiesを定義する

App Entitiesはアプリケーションの「名詞」、つまりintentが操作対象とするオブジェクトを表します。これによりSiriはアプリのデータを理解し、検索し、操作できます。効果的なentityの構築は、intent駆動型アプリにおけるSwiftUI状態管理パターンにとって不可欠です。

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が名前でentityを見つけられるようにします。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"
        )
    }
}

音声フレーズには、Siriが対象アプリを識別するために(.applicationName)プレースホルダーを含める必要があります。(.)のような動的パラメータは文脈に応じたコマンドを可能にします。

インタラクティブウィジェットでのintents

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フレームワーク統合

iOS 27はFoundation Modelsフレームワークを導入し、オンデバイスでの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以降が必要です。

SiriKitからApp Intentsへの移行

まだSiriKitを使用しているアプリの場合、移行は構造化されたプロセスに従います。Xcode 27はWidget設定用の「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フレームワークは、モックなしで実際のSiri、ショートカット、Spotlightインフラストラクチャを通じてintentsを検証します。

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の動作を検証し、デプロイ前にリグレッションを検出します。

Sources

App Intents iOS 27実装チェックリスト

iOS 27をターゲットとするアプリにとって、App Intentsはもはや任意ではありません。Siri AIはアプリ間でマルチステップアクションを構成し、公開されたintentsのないアプリはこれらのエージェントワークフローから除外されます。

  • アプリの主要なアクションのためのAppIntentsを作成する
  • 操作可能なデータのためのAppEntitiesを定義する
  • 列挙型にはAppEnumを使用する
  • 音声フレーズを伴うAppShortcutsProviderを実装する
  • 最大10個のApp Shortcutsの上限を遵守する
  • すべてのフレーズに(.applicationName)を含める
  • 画面上の参照のためにSwiftUIビューにView Annotationsを追加する
  • 長時間実行操作のためにストリーミングレスポンスを実装する
  • 複雑なワークフローのためにマルチターン会話をサポートする
  • 非推奨期間が終了する前にSiriKitから移行する
  • AppIntentsTestingフレームワークでintentsをテストする
  • タイトルと説明をローカライズする

今すぐ練習を始めましょう!

面接シミュレーターと技術テストで知識をテストしましょう。

今日のチャレンジ

iOS のバグを見つけられますか

実際のコード、隠れたバグ、1日1回。アカウントなしで試せます。

Anthony Fillion-Maillet

執筆

Anthony Fillion-Maillet

SharpSkill 創業者

10 年以上フルスタック開発に携わっています。SharpSkill を運営し、ここで公開される内容に責任を負っています。

2026年8月20日 更新

タグ

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

共有

関連記事