App IntentsとSiri Shortcuts: iOS 2026の高度な自動化

iOS 18+向けApp IntentsとSiri Shortcutsの完全ガイドです。カスタムSiriアクションの構築、Apple Intelligenceの統合、2026年のSwiftアプリ自動化を解説します。

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

2026年、Apple IntelligenceとApp Intentsフレームワークにより、iOSアプリはユーザーの意図がグラフィカルインターフェースよりも重要となる時代に入ります。intentを公開しないアプリは、AI中心のOSでは見えない存在になってしまいます。App Intentsは、Siri、Spotlight、ウィジェット、Action Buttonがアプリの機能と対話するための基盤を構成します。

本記事の内容

本記事では、iOS 18+向けのApp IntentsとSiri Shortcutsの作成を、基本概念からApple IntelligenceとApp Intent Domainsへの統合まで、完全に紹介します。

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

iOS 16で導入されたApp Intentsフレームワークは、古いSiriKit Intentsフレームワークを置き換え、Swiftにおけるintentの構築を近代化します。この宣言的アーキテクチャにより、システムが検出可能なアクション(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
        )

        // チェーン用に作成したentityを返す
        return .result(value: TaskEntity(task: task))
    }
}

intentはプロパティラッパー@Parameterによりパラメータを宣言し、Siriが不足値を尋ねられるようにします。perform()メソッドはビジネスロジックを実行し、型付けされた結果を返します。

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

App Entitiesはアプリケーションの「名詞」、つまりintentが操作対象とするオブジェクトを表します。これによりSiriはアプリのデータを理解し操作できます。

TaskEntity.swiftswift
import AppIntents

// 内部データモデル
struct Task: Identifiable, Codable {
    let id: UUID
    var title: String
    var priority: TaskPriority
    var isCompleted: Bool
    var dueDate: Date?
}

// システムに公開するentity
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")
        )
    }

    // entity検索のデフォルトクエリ
    static var defaultQuery = TaskEntityQuery()

    // 内部モデルからのイニシャライザ
    init(task: Task) {
        self.id = task.id
        self.title = task.title
        self.priority = task.priority
        self.isCompleted = task.isCompleted
    }
}

// entityを検索・フィルタするクエリ
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はシステムがentityを検索・提案する方法を定義します。entities(for:)suggestedEntities()の各メソッドはSiriおよびショートカットのインターフェースに供給されます。

App Enum と App Entity

固定値の集合を持つ型(優先度、ステータス)にはAppEnumを使用し、ユーザーが作成する動的な型(タスク、メモ、連絡先)にはAppEntityを使用します。

固定値のためのApp Enumsを作成する

App Enumsは列挙型をシステムに公開し、Siriが文脈に応じた選択肢を提示できるようにします。

TaskPriority.swiftswift
import AppIntents

// システムに公開する列挙型
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 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

// アプリのすべてのショートカットを宣言するプロバイダ
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: [
                "\(.applicationName)\(\.$taskName)を完了",
                "\(.applicationName)\(\.$taskName)を完了済みにする"
            ],
            shortTitle: "タスクを完了",
            systemImageName: "checkmark.circle"
        )
    }
}

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

ショートカット10個の上限

アプリケーションは最大10個のApp Shortcutsを宣言できます。ユーザーにとって最も頻繁で有用なアクションを優先することが望まれます。

複雑なパラメータを持つintents

intentsはentityや構成オプションを含む複雑なパラメータを受け取れます。

CompleteTaskIntent.swiftswift
import AppIntents

struct CompleteTaskIntent: AppIntent {
    static var title: LocalizedStringResource = "タスクを完了"

    static var description = IntentDescription(
        "タスクを完了済みとしてマークします。"
    )

    // 自動検索付きのentityパラメータ
    @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)を完了済みとしてマークしました。"
        )
    }
}

// entityのリストを返す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は、特定の機能向けに設計されたAPI群であるApp Intent Domainsを導入します。これらのドメインにより、Apple Intelligenceはより高い精度でアクションを理解し実行できます。

BookmarkDomainIntents.swiftswift
import AppIntents

// Apple Intelligence統合のためBookmarksドメインに準拠
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()
    }
}

// インタラクティブボタン付きのウィジェットビュー
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の設定

iPhone 15 Pro以降のAction Buttonは、App Shortcutsを直接トリガーできます。

ActionButtonIntent.swiftswift
import AppIntents

// Action Button向けに最適化されたintent
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())
    }
}

// Action Button向けにAppShortcutsProviderで宣言
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を導入します。App IntentsはSwift Packagesに置いて、複数プラットフォームで再利用することもできます。

Swift Packagesにおけるintents

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)を含める
  • ✅ SiriフィードバックのためのParameterSummaryを構成する
  • ✅ 該当する場合はApple Intelligenceドメインを統合する
  • ✅ ユニットテストでintentsをテストする
  • ✅ タイトルと説明をローカライズする

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

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

タグ

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

共有

関連記事