# App IntentsとSiri Shortcuts: iOS 2026の高度な自動化 > iOS 18+向けApp IntentsとSiri Shortcutsの完全ガイドです。カスタムSiriアクションの構築、Apple Intelligenceの統合、2026年のSwiftアプリ自動化を解説します。 - Published: 2026-03-08 - Updated: 2026-04-29 - Author: SharpSkill - Tags: app-intents, siri-shortcuts, ios, swift, apple-intelligence - Reading time: 15 min --- 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)を作成できます。 ```swift // TaskIntent.swift 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 { // サービス経由でタスクを作成 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はアプリのデータを理解し操作できます。 ```swift // TaskEntity.swift 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が文脈に応じた選択肢を提示できるようにします。 ```swift // TaskPriority.swift 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に表示されます。 ```swift // ShortcutsProvider.swift 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や構成オプションを含む複雑なパラメータを受け取れます。 ```swift // CompleteTaskIntent.swift 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をどのように提示するかを定義し、自然なフィードバックを提供します。 ## Apple Intelligenceとの統合 iOS 18は、特定の機能向けに設計されたAPI群であるApp Intent Domainsを導入します。これらのドメインにより、Apple Intelligenceはより高い精度でアクションを理解し実行できます。 ```swift // BookmarkDomainIntents.swift 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アクション 機微なアクションでは、システムは実行前に確認を求めることができます。 ```swift // DeleteTaskIntent.swift 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+のインタラクティブウィジェットを作成します。 ```swift // TaskWidgetIntents.swift 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を直接トリガーできます。 ```swift // ActionButtonIntent.swift 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間で共有できます。 ```swift // Package.swift // 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コードと同様にユニットテストできます。 ```swift // TaskIntentTests.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と最適なユーザー体験を保証します。 ```swift // BestPractices.swift 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をテストする - ✅ タイトルと説明をローカライズする --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/ja/blog/ios/app-intents-siri-shortcuts-advanced-ios-automation