SiriKitを試す

こんにちは。E・レシピ チームの小林です。
普段はE・レシピのiOSアプリやサーバーサイドの開発を担当しています。
先月WWDC16が開催されましたが、今年弊社からは自分を含めたiOSエンジニア2人で参加してきました。
今年も多くの発表がありましたが、今回はその一つのSiriKitについて試してみたことをまとめてみました。

SiriKitができること

iOS10からSiriからアプリを操作できるようになります。

なので例えば、

といったフレーズでSiriを使えたらいいなと思いますが、
残念なことに現段階では以下の6つのDomainに関連したフレーズでないとこの機能は使えないようです。

  • Audio or video calling
  • Messaging
  • Payments
  • Searching photos
  • Workouts
  • Ride booking

それぞれのDomainにいくつかIntent(アクション)がClassで定義されています。
Intents Domains

SiriKitを試す_f0364156_19035852.png
文章から、Domain, Intent, パラメーターを読み取っている例
SiriKitを試す_f0364156_19094045.png
メッセージを送信するだけでも、全言語で何通りもある言い方のバリエーションに対応しないといけません。

上記のセッションでも説明している通り、Siriは音声データをテキストデータに変換した後に、テキストデータからDomain, Intent, 各Intent固有のパラメーターを読み取る必要があります。
単に読み取ると言っても、ひとつのIntentを表現するフレーズでも何通りもあり、さらにそれを全ての言語に対応させなければならないのが非常に大変そうなので、少しずつ対応Domainを増やしていくしかないだろうなと思います。


Siriの処理の流れ

SiriKitを試す_f0364156_19153849.png
Siriの処理の流れについて簡単に説明します。
Intentが、受け取ったテキストデータを処理するフェーズはResolve, Confirm, Handleの3つがあります。

Resolve

後の処理に必要なパラメーターをチェックするフェーズです。それぞれのIntentに含まれるパラメーターごとに用意されたResolveメソッドの返り値を調整することで、必須パラメーターのチェックや曖昧性の排除などをSiriが対話的に行ってくれ、以下のようなものがあります。

success

パラメーターが問題ない場合はsuccessを返すと次のフェーズに進みます

confirmationRequired

SiriKitを試す_f0364156_19241741.png
Yes or No で答えさせる

disambiguation

SiriKitを試す_f0364156_19241738.png
設定された選択肢の中から選択させる

needsValue

SiriKitを試す_f0364156_19241732.png
パラメーターが足りないことを伝える

Confirm

SiriKitを試す_f0364156_19241780.png

ユーザーから適切なパラメーターを受け取った後の最終確認フェーズです。
ここのビューは独自に実装することができます。

Handle

SiriKitを試す_f0364156_19241749.png

アプリ内で決済やメッセージ送信の処理を行うフェーズです。
処理中の場合はスピナーを表示させたり、正常に処理が終了した時はそれがわかるUIにすることが大切です。
Siri上で処理を完結させずに、NSUserActivityを使って情報をアプリに渡して遷移させることも可能です。




SiriKitを試してみる

今回は、弊社のiOSアプリの 寝たまんまヨガ をSiriからハンドリングできるかどうか試してみました。
寝たまんまヨガは、リラクゼーションや瞑想などのいろいろなヨガのプログラムを楽しめるアプリです。
以下のフレーズでSiriからインテントハンドリングしたいと思います。

フレーズ寝たまんまヨガでディープリラクゼーションを開始
DomainWorkouts
Intent INStartWorkoutIntent
App寝たまんまヨガ
workoutNameディープリラクゼーション

これでうまくいくとよかったですが、残念ながら、色々と試してみたところまだWorkoutsドメインの日本語対応が不十分なのか、日本語のフレーズではWorkoutsのインテントハンドリングをすることができませんでした。。少し残念でしたが、英語でのハンドリングには成功したので、以下にそれについてまとめてみました。ここではアプリの英語名を「Excite Yoga」にしています。

フレーズStart the Deep Relaxation with Excite Yoga
DomainWorkouts
Intent INStartWorkoutIntent
AppExcite Yoga
workoutNameDeep Relaxation

Intents Extensionを追加する

SiriKitを使うためには、Intents Extensionを既存のアプリに追加します。
追加されたExtensionのInfo.plistのIntentsSupported内にサポートするIntentのクラス名を追加します。

<key>IntentsSupported</key>
<array>
<string>INStartWorkoutIntent</string>
</array>

Siriからの使用を許可するアラートを表示させる

Intents Extensionを有効にするためには、ユーザーからの許可が必要なのでそのためのアラートを任意のタイミングで表示させます。

INPreferences.requestSiriAuthorization { status in }

アプリの方のInfo.plistのNSSiriUsageDescriptionにアラートに含める説明文を追加します。


<key>NSSiriUsageDescription</key>
<string>説明文を追加</string>

独自の語彙を追加する

SiriKitにはアプリ独自の語彙を追加する機能があります。
App Vocabulary File Format

今回はSiriがDeep RelaxationをworkoutNameとして認識するように設定しました。
Intent Phrasesに例文を追加すると、Siriのアプリごとの使い方画面にそれが表示されます。

AppIntentVocabulary.plistファイルをアプリに追加します。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>ParameterVocabularies</key>
<array>
<dict>
<key>ParameterNames</key>
<array>
<string>INStartWorkoutIntent.workoutName</string>
</array>
<key>ParameterVocabulary</key>
<array>
<dict>
<key>VocabularyItemIdentifier</key>
<string>deep_relaxation</string>
<key>VocabularyItemSynonyms</key>
<array>
<dict>
<key>VocabularyItemPhrase</key>
<string>Deep Relaxation</string>
<key>VocabularyItemPronunciation</key>
<string>deep relaxation</string>
<key>VocabularyItemExamples</key>
<array>
<string>Start the deep relaxation with Excite Yoga</string>
</array>
</dict>
</array>
</dict>
</array>
</dict>
</array>
<key>IntentPhrases</key>
<array>
<dict>
<key>IntentName</key>
<string>INStartWorkoutIntent</string>
<key>IntentExamples</key>
<array>
<string>Start the deep relaxation with Excite Yoga</string>
</array>
</dict>
</array>
</dict>
</plist>

Intent Handler の実装

今回はSiriがworkoutNameを聞き取れなかった場合は、あらかじめ定義した選択肢から選択させるようにresolve部分を実装しました。

workoutの配列を宣言

let workoutNames = [
"Deep Relaxation",
"Chakra Mediation",
"Prana Relaxation",
"Natures Grace",
"Forest Walk"
]

Intentごとに定義されているプロトコルメソッドを実装します。
INStartWorkoutIntentにはINStartWorkoutIntentHandlingプロトコルが定義されています。

resolveメソッド

func resolveWorkoutName(forStartWorkout intent: INStartWorkoutIntent, with completion: (INStringResolutionResult) -> Void) {
guard let workoutName = intent.workoutName where workoutNames.contains(workoutName) else {
completion(INStringResolutionResult.disambiguation(with: workoutNames))
return
}

completion(INStringResolutionResult.success(with: workoutName))
}

該当するworkoutNameでない場合は、 .disambiguationを返すとSiriがユーザーに選択肢を提示してくれます。

handleメソッド

func handle(startWorkout startWorkoutIntent: INStartWorkoutIntent, completion: (INStartWorkoutIntentResponse) -> Void) {
guard let workoutName = startWorkoutIntent.workoutName else {
completion(INStartWorkoutIntentResponse(code: .failure, userActivity: nil))
return
}
let userActivity = NSUserActivity(activityType: String(INStartWorkoutIntent))
userActivity.userInfo = ["workoutName": workoutName]
completion(INStartWorkoutIntentResponse(code: .success, userActivity: userActivity))
}

ここでNSUserActivityに設定した情報は、UIApplicationDelegateの

func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: ([AnyObject]?)

メソッド内で受け取ることができます。


テスト

これで実装が終わったので実際に使ってみます。
まずはworkoutNameは言わずに、

Start something with Excite Yoga

と試してみると、

Which one?
---
Deep Relaxation
Chakra Mediation
Prana Relaxation
Natures Grace
Forest Walk

と、disambiguationで設定したリストを表示してくれました。
ここで、

Deep Relaxation

と答えると、handleメソッドが実行された後にアプリが起動しました。
ここでuserActivityをみてみると、

(lldb) p userActivity.userInfo
([NSObject : AnyObject]?) $R0 = 1 key/value pair {
[0] = {
key = 0xe0000000170032820 "workoutName"
value = 0x0000000170055750 "Deep Relaxation"
}
}

という具合に、しっかりアプリ内でworkoutNameを受け取れています。

次はworkoutNameを省略せずに、

Start the Deep Relaxation with Excite Yoga

と試したところ一発でアプリが起動しました。


まとめ

今回はSiriKitを使って、寝たまんまヨガをWorkoutsドメインでインテントハンドリングする例を紹介しました。
日本語でインテントハンドリングすることができなくて残念でしたが、対応している言語であれば、その言語にローカライズされたAppIntentVocabularyファイルを追加するだけで多言語対応できそうです。
さらにIntents UI Extensionを追加すれば、カスタムUIをSiri上で表示することができるのでアプリに遷移することなく処理を完結させることもできます。Providing a Custom Interface
まだ対応ドメインが6つだけと少ないですが、ユーザビリティ向上のために実装しておきたい機能だと思いました。


参考

Session

Introducing SiriKit
Extending Your Apps with SiriKit

ドキュメント

SiriKit Programming Guide
Intents Domains
App Vocabulary File Format
Providing a Custom Interface



エンジニア募集

エキサイトではエンジニアとして一緒に働いてくださる方を新卒採用と中途採用で募集しています。詳しくは、こちらの採用情報ページをご覧ください。

by ex-engineer | 2016-07-11 10:00