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

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)を作成できます。
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はアプリのデータを理解し操作できます。
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およびショートカットのインターフェースに供給されます。
固定値の集合を持つ型(優先度、ステータス)にはAppEnumを使用し、ユーザーが作成する動的な型(タスク、メモ、連絡先)にはAppEntityを使用します。
固定値のためのApp Enumsを作成する
App Enumsは列挙型をシステムに公開し、Siriが文脈に応じた選択肢を提示できるようにします。
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に表示されます。
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個のApp Shortcutsを宣言できます。ユーザーにとって最も頻繁で有用なアクションを優先することが望まれます。
複雑なパラメータを持つintents
intentsはentityや構成オプションを含む複雑なパラメータを受け取れます。
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はより高い精度でアクションを理解し実行できます。
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アクション
機微なアクションでは、システムは実行前に確認を求めることができます。
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+のインタラクティブウィジェットを作成します。
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を直接トリガーできます。
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は、ボイラープレートを削減するマクロ@DeferredPropertyと@ComputedPropertyを導入します。App IntentsはSwift Packagesに置いて、複数プラットフォームで再利用することもできます。
Swift Packagesにおけるintents
App IntentsはSwift Packagesで定義し、iOS、macOS、watchOS間で共有できます。
// 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コードと同様にユニットテストできます。
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と最適なユーザー体験を保証します。
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をテストする
- ✅ タイトルと説明をローカライズする
今すぐ練習を始めましょう!
面接シミュレーターと技術テストで知識をテストしましょう。
タグ
共有
関連記事

WidgetKit iOS 17+:App Intentsによるインタラクティブウィジェット
WidgetKitとApp IntentsでインタラクティブなiOSウィジェットを作成する完全ガイド。2026年のiOS 17+向けボタン、トグル、アニメーション、ベストプラクティス。

Swift における Combine vs async/await:段階的な移行パターン
Swift で Combine から async/await への移行を進めるための完全ガイド:段階的な戦略、ブリッジングパターン、iOS コードベースにおけるパラダイムの共存。

2026年のiOSアクセシビリティ面接質問: VoiceOverとDynamic Type
iOS面接に向け、VoiceOver、Dynamic Type、セマンティックtraits、監査などアクセシビリティの重要質問を解説します。