App Intents はじめの三歩
App Intents とは
- アプリの「意図(インテント)」をシステムに伝えるもの
- アプリの機能をアプリ外の様々な場所から使えるようになる
WWDC24 "Bring your app’s core features to users with App Intents" より
App Intentsに「今」取り組むモチベーション
App Intents対応 ≒ Apple Intelligence対応
Siriがアプリの機能を使ってくれるようになる(iOS 18.?)
WWDC24 "Bring your app’s core features to users with App Intents" より
どの機能をインテントとして切り出せばいいの?
→ 全部
WWDC24 "Design App Intents for system experiences" より
アシスタントスキーマ(Assistant Schemes)の話
WWDC24 "Bring your app’s core features to users with App Intents" より
- 概要:Apple Intelligenceに対応する
- 11/19開催のイベントで実践的な話をする予定
本発表の位置付け
- App Intentsの概念とApple Intelligenceの関係はわかった
- 「全機能切り出そう」というAppleの言い分もわかった
- でもどこから始めたらいいの?
→ はじめの三歩目 [1] ぐらいまでをナビゲーションする
一歩目: アプリを開くだけのインテント
実装
// AppIntentに準拠
struct OpenAppIntent: AppIntent {
// タイトルは必須
static var title: LocalizedStringResource = "Open Hoge"
// インテント実行時にアプリを起動する
static var openAppWhenRun: Bool = true
// インテント実行時の処理
func perform() async throws -> some IntentResult {
// 何もしない
return .result()
}
}
- 既存実装に手をいれる必要がない
- 実質5行程度
動作確認
- インテント ≒ (ショートカットアプリにおける)アクション
- アプリをインストールするだけでリストに出てくるようになる
二歩目: 入力を持つインテント
動画ファイルを入力に受け取ってアプリを開くインテント
インテント(アクション)を実行 → 動画選択 → 選択した動画ファイルを入力としてアプリ起動
実装
struct TrimSilenceIntent: AppIntent {
static var title: LocalizedStringResource = ...
static var openAppWhenRun: Bool = true
// 入力パラメータとして動画ファイルを受け取る
@Parameter(title: "Video File", supportedTypeIdentifiers: ["public.movie"])
var video: IntentFile
@MainActor
func perform() async throws -> some IntentResult {
// 動画ファイルのURLにアクセス
guard let videoURL = video.fileURL else { ... }
// 実際の処理
...
return .result()
}
}
@Parameter
プロパティラッパー
@Parameter(title: "Video File", supportedTypeIdentifiers: ["public.movie"])
var video: IntentFile
これを指定したプロパティは、インテントのパラメータ(= アクションのパラメータ)としてショートカットアプリに表示されるようになる
`@Parameter` プロパティラッパーの定義
-
IntentParameter
のtypealias
public typealias Parameter = IntentParameter
-
IntentParameter
の定義:
@propertyWrapper final public class IntentParameter<Value> : @unchecked Sendable where Value : _IntentValue, Value : Sendable {
final public let defaultValue: Value.UnwrappedType?
final public let title: LocalizedStringResource
final public var isOptional: Bool { get }
final public var projectedValue: IntentParameter<Value> { get }
final public var wrappedValue: Value
}
-
@Parameter(title:supportedTypeIdentifiers:)
に相当するイニシャライザの定義:
public convenience init(title: LocalizedStringResource, description: LocalizedStringResource? = nil, default defaultValue: Value.UnwrappedType? = nil, supportedTypeIdentifiers: [String] = ["public.item"], requestValueDialog: IntentDialog? = nil, inputConnectionBehavior: InputConnectionBehavior = .default)
- ドキュメント
A property wrapper that indicates the associated property is an input argument of the app intent.
三歩目: 出力を持つインテント
動画から音声を抽出するインテント
インテント(アクション)を実行 → 動画選択 → 選択した動画ファイルを入力 → 音声を抽出(バックグラウンド処理)
→ 音声ファイルを出力
実装
struct ExtractAudioIntent: AppIntent {
static var title: LocalizedStringResource = ...
static var description = ...
@Parameter(title: "Video File", supportedTypeIdentifiers: ["public.movie"])
var video: IntentFile
// IntentFile型を返すことを明示
func perform() async throws -> some IntentResult & ReturnsValue<IntentFile> {
guard let videoURL = video.fileURL else { ... }
let audioFileUrl: URL = ...
// 動画から音声データを抽出して audioFileUrl に保存
...
// 音声ファイルを `IntentFile` として出力
let intentFile = IntentFile(fileURL: audioFileUrl)
return .result(value: intentFile)
}
}
ReturnsValue
の型をちゃんと指定する
ポイント1: - before
func perform() async throws -> some IntentResult
→ エラーになる:
Fatal error: perform() returned types not declared in method signature
- after
func perform() async throws -> some IntentResult & ReturnsValue<IntentFile>
→ OK
perform() メソッドのと ReturnsValue の定義
-
perform()
メソッドの定義: 戻り値の型はPerformResult
func perform() async throws -> Self.PerformResult
-
PerformResult
はIntentResult
のassociatedtype
associatedtype PerformResult : IntentResult
-
ReturnsValue
はIntentResult
に準拠
public protocol ReturnsValue<Value> : IntentResult
IntentFile
型でファイル出力
ポイント2: ReturnsValue<URL>
でファイルURLを出力しても、そのファイルを後段のインテント(アクション)で利用できない
func perform() async throws -> some IntentResult & ReturnsValue<URL> {
...
return .result(value: audioFileUrl)
}
ReturnsValue<IntentFile>
で、ファイルを出力する
func perform() async throws -> some IntentResult & ReturnsValue<IntentFile> {
...
let intentFile = IntentFile(fileURL: audioFileUrl)
return .result(value: intentFile)
}
→ 後段のアクション(例:サウンドを再生
/ Play sound
)で、抽出した音声ファイルを利用可能に
まとめ
- 一歩目:アプリを起動するだけのインテント
- 既存実装に手をいれる必要がない
- 実質5行程度
- 二歩目:入力を持つインテント
-
@Parameter
プロパティラッパー
-
- 三歩目:出力を持つインテント
-
ReturnsValue
で出力する型を指定する -
IntentFile
型でファイル出力
-
おわりに
本記事で取り上げているApp Intentsは、Chopper というiOSアプリをインストールすることで実際に試せます。
人の声だけを抽出して無音区間を除去するアプリ、Chopper
-
一歩目だけだとその先に困る、三歩ぐらいやるとユーザー体験として意味のあるインテントがつくれるようになる) ↩︎
Discussion