【Swift/iOS】App Intentsの実装方法!ショートカットやSiriの実装
この記事からわかること
- Swift/iOSのApp Intentsの実装方法
- ショートカットやSiriで操作を実行するには?
- IntentResultプロトコルのApp 使い方
index
[open]
\ アプリをリリースしました /
友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-
posted withアプリーチ
環境
- Xcode:16.0
- iOS:18.0
- Swift:5.9
- macOS:Sonoma 14.6.1
公式リファレンス:App Intentでアプリのコア機能をユーザーに提供 - WWDC2024
App Intentsフレームワーク
「App Intentsフレームワーク」はアプリがSiriやショートカット、ウィジェットと連携するための機能を提供しているフレームワークです。アプリで提供している機能やデータに対してユーザーが音声コマンドやショートカットを経由してアクセスできるようになります。
実際にコードベースで見るとSiriやショートカットと連携するためには「App Intent」という特定のインターフェースを定義する必要があります。
App Intent
App Intentはアプリで実行可能な動作を示すクラスでショートカットやSiriなどのシステム機能を通じて外部からアプリの機能を利用できるようにします。AppIntent
プロトコルが用意されており、これを継承してクラスを定義しておくことで反映されるようになります。
struct AppTestIntent: AppIntent {
/// インテントのタイトル
static var title = LocalizedStringResource("インテントタイトル")
/// インテントの説明(未定義でもOK)
static var description = "インテントの説明だよ"
/// インテント実行時にアプリを起動させるかどうか(未定義でもOK/未定義の場合はfalseと同義)
static var openAppWhenRun: Bool = true
/// インテント実行時に実行したい処理
func perform() async throws -> some IntentResult {
return .result()
}
}
AppIntent
プロトコルを継承させて必須になるのはインテントのタイトルを定義するtitle
プロパティとインテント実行時に実行したい処理を定義するperform
メソッドです。
ショートカットアプリに表示
AppIntent
プロトコルを継承したクラスは定義するだけで勝手にショートカットアプリに表示されるようになります。ショートカットアプリを起動して右上の「 + 」からアクションを追加する際にアプリ一覧に対象のアプリ(例:TestApp)が表示されるようになります。
アプリを選択すると定義されているインテントが表示されます。title
に指定した部分が一覧で表示され、description
で指定した値は「」をクリックすると表示されます。このインテントを追加して実行してみると今回の場合は処理は空でopenAppWhenRunにtrueを指定しているのでアプリのみ起動します。
Siriでの呼び出し
ショートカットに追加したインテントはSiriでも呼び出しが可能になります。例えば以下の場合は「Hey Siri インテントタイトル」で反応してインテントを実行してくれるようになります。タイトル部分に指定した文言で呼び出せるようになるみたいですね。
インテントで実行する処理を定義する:IntentResultプロトコル
インテントで実行する処理の定義はperform
メソッドで行うことを説明しました。先ほどの実装を見てみると返却する値は見慣れないsome IntentResult
という型になっています。some
は型を隠蔽するため付与されておりここでは「具体的な型は非公開だがIntentResultプロトコルに準拠した何らかの型」であることを表します。
/// インテント実行時に実行したい処理
func perform() async throws -> some IntentResult {
return .result()
}
result
メソッドがインテント実行完了後の動作を定義する部分になります。これにはいろいろな引数がありそれによって処理を変更することが可能です。
結果を返す:.result(value:)
実行した際に何かしらの結果を返したい場合は.result(value:)
を使用します。値を返す場合は返り値の型をsome IntentResult & ReturnsValue<返却する型>
に変更します。
struct ReturnResultIntent: AppIntent {
static var title = LocalizedStringResource("Return Result")
/// 返り値の型を変更する
/// some IntentResultのままでもビルドできてしまうが実行時にエラーになる
/// Thread 5: Fatal error: perform() returned types not declared in method signature
func perform() async throws -> some IntentResult & ReturnsValue<String> {
if Int.random(in: 0 ... 1) == 1 {
return .result(value: "成功")
} else {
return .result(value: "失敗")
}
}
}
ここで返却した値はショートカットで何かしらの処理の流れを実装する際に後続の処理に渡すことが可能になります。例えば「成功が返ればTestFlightアプリを起動する」なら以下のようになります。
結果をダイアログで表示する:.result(value:, dialog:)
インテントの実行完了時に何かしらのメッセージをダイアログでユーザーに表示したい場合は.result(value:, dialog:)
を使用します。返り値にはさらにProvidesDialog
を追加します。
struct ReturnResultDialogIntent: AppIntent {
static var title = LocalizedStringResource("Return Result Dialog")
/// IntentDialogの引数に表示したいメッセージを渡す
func perform() async throws -> some IntentResult & ReturnsValue<String> & ProvidesDialog {
if Int.random(in: 0 ... 1) == 1 {
return .result(value: "成功", dialog: IntentDialog("成功"))
} else {
return .result(value: "失敗", dialog: IntentDialog("失敗"))
}
}
}
実行すると以下のようなダイアログが表示されます。もちろん結果も返却しているので後続の処理に渡すことも可能です。
カスタムダイアログで表示する:.result(value:, dialog:, view:)
カスタムダイアログでユーザーに表示したい場合は.result(value:, dialog:, view:)
を使用します。返り値にはさらにShowsSnippetView
を追加します。
struct ReturnResultCustomDialogIntent: AppIntent {
static let title: LocalizedStringResource = "Return Result Custom Dialog"
func perform() async throws -> some IntentResult & ReturnsValue<String> & ProvidesDialog & ShowsSnippetView {
let result = "成功"
return .result(
value: result,
dialog: "カスタムダイアログタイトル",
view: CustomDialogView(result: result))
}
private func CustomDialogView(result: String) -> some View {
HStack {
Text("成功")
.padding()
.frame(width: 100)
.foregroundStyle(.white)
.background(result == "成功" ? Color.green : Color.gray)
.clipShape(RoundedRectangle(cornerRadius: 20))
Text("失敗")
.padding()
.frame(width: 100)
.foregroundStyle(.white)
.background(result == "失敗" ? Color.green : Color.gray)
.clipShape(RoundedRectangle(cornerRadius: 20))
}
}
}
アプリ内に保持しているデータを返却する
定義したインテントの中からアプリ内に保持しているデータにも問題なくアクセスできるようです。
struct FetchDataDialogIntent: AppIntent {
static var title = LocalizedStringResource("Fetch Data Dialog")
func perform() async throws -> some IntentResult & ReturnsValue<String> & ProvidesDialog {
// UserDefaultsからデータを取得する
let result = UserDefaultsRepository.sheard.getStringData(key: UserDefaultsKey.KEY_DATA)
return .result(value: "保存しているデータ", dialog: IntentDialog(stringLiteral: result))
}
}
ショートカットにデフォルトで登録する
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。