[iOS 11] Siriとアプリをつなげる!SiriKitを使ったVoice User Interface開発のまとめ

400x400_ios11_siri

SiriKitとは

SiriKitとはiOSアプリをSiriに対応させることができる、一連のフレームワークです。iOSアプリに組み込むことで、Siriとの対話によってiOSアプリのコンテンツ表示や、特定の処理を実行させることができます。

SiriKitはiOS 10から使えるようになりました。何でもかんでもできるわけではありませんが、決められた機能の範囲であればSiriを通してアプリの機能にアクセスさせることができます。iOS 11では、できることがさらに増えました(詳しくは後述)。

開発者は自身のiOSアプリを、昨今の流行であるパーソナルアシスタントおよび Voice User Interface(VUI)の機能の一部に加えることができます。

SiriKitの基礎知識

SiriKitを扱うには、いくつかの基礎知識を理解しておく必要があります。1つずつ学んでいきましょう。

IntentsフレームワークとIntents UIフレームワーク

SiriKitという名前ではありますが、実際にはSiriKitというフレームワークは存在しません。下記の2つのフレームワークから成り立ちます。

  • Intentsフレームワーク
  • Intents UIフレームワーク

Intentsフレームワークは、iOSとiOSアプリがやりとりするための基本機能を提供します。一方Intents UIフレームワークは、Siri経由で実行した処理の結果をカスタマイズしたビューとしてスクリーンに表示させる機能を提供します。

Intents ExtensionとIntents UI Extension

SiriKitは、iOSアプリにIntents ExtensionまたはIntents UI Extension(App Extensionの一種)という形で組み込みます。App ExtensionとはiOSアプリの機能をアプリ外に拡張するための仕組みです。詳しくは下記を参照してください。

Intents ExtensionはユーザーがSiriを使って要求したリクエストを受け取り、iOSアプリ固有のアクションを実行します。一方Intents UI Extensionはユーザーからのリクエストを処理した後、その実行結果をレスポンスとしてカスタマイズしたコンテンツを表示します。

SiriKitを組み込む上でIntents Extensionの実装は必須ですが、Intents UI Extensionは任意 (表現に必要であれば組み込む) です。

ドメインとIntent

Extension(Intents Extension と Intents UI Extension)をiOS アプリに組み込むことで、iOSアプリが起動していない状態でも、Siriを経由したリクエストに応答できるようになります。

このExtensionをiOSアプリに組み込むことになりますが、その際にドメインIntent (インテント)を登録します。

ドメインは「リクエストの種類」、Intentは「リクエストに応じて行うアクション」のことです。例えばメッセージアプリにSiriKitに組み込むと「○○○さんに△△△と送信して」といった指示をSiriに依頼できるようになります。このような場合、ドメインは「メッセージ」、Intentは「メッセージを送信する」といった形になります。

iOS 10とiOS 11では、下表のドメインが指定できます。iOS 11からは新たにメモの作成タスクの登録QR コードの表示などができるようになりました。

ドメインの名前 できること (作ることができる Intent)
VoIP Calling 通話の開始、通話履歴の検索
Massaging メッセージの送信、受信メッセージの検索
Payments ユーザー間の支払い、請求書の支払い
Lists and Notes (NEW!) メモの作成・検索、タスクの作成・検索
Visual Codes (NEW!) 連絡先と支払い情報の QR コードの表示
Photos 写真の検索と表示
Workouts フィットネスのためのルーチンワークの開始・終了・管理
Ride Booking 乗車とステータスの報告
Car Commands 車両のドアロックの管理、車両の状態の取得
CarPlay 車両の CarPlay システムとの連係
Restaurant Reservations レストランの予約の作成と管理

各ドメインおよびIntentの詳細な解説は、下記を参照してください。

Intents Extensionの仕組み

Intents Extensionが動作する仕組みを学んでいきましょう。

Intents Extensionが動作する上で必ず必要となるのがINExtensionオブジェクトです。INExtensionはIntents Extensionのエントリーポイントであり、Intentの実行が必要になったタイミングでSiriKitから(つまり、ユーザーがSiriを通して依頼を行ったときに)呼び出されます。

INExtensionオブジェクトはSiriKitからの呼び出しに対し、Intentを実際に処理するオブジェクトに振り分けることを行います。具体的には、下記のようなメソッドを実装します。

class IntentHandler: INExtension {
    override func handler(for intent: INIntent) -> Any {
        return self
    }
} 

このhandler(for:)メソッドはSiriKitからIntentの情報が詰め込まれたINIntentオブジェクト付きで呼び出されます。INIntentオブジェクトにはSiriがユーザから依頼されたときに得たデータが収容されています。

このメソッドの戻り値には、Intentに対応するhandlerオブジェクトを渡します。handlerオブジェクトは、各ドメインに定義されているプロトコルに準拠したオブジェクトです。例えば「メッセージを送信する」というIntentに対応させる場合、INSendMessageIntentHandlingプロトコルに準拠したオブジェクトを用意する必要があります。

handlerオブジェクトには、Intentを受け取り、必ずレスポンスを返す実装が必要となります。この処理のことをハンドルと呼びます。ハンドルするためのメソッドは、各ドメインに定義されているプロトコル全てに実装が必須なメソッドとして定義されています。例えば「メッセージを送信する」というIntentをハンドルするためのメソッドは、次のように実装します。

extension IntentHandler: INSendMessageIntentHandling {
    func handle(intent: INSendMessageIntent, completion: @escaping (INSendMessageIntentResponse) -> Void) {
        let response = INSendMessageIntentResponse(code: .success, userActivity: nil)  completion(response)
    }
} 

ハンドルするためのメソッドの引数は、原則的にIntentオブジェクトとコールバック用の関数オブジェクトが定義されています。これは、全てのドメインで共通です(扱うクラスがIntentごとに異なるため、各プロトコルで個別にルールに従うように定義されている)。「メッセージを送信する」というIntentの場合はINSendMessageIntentResponseを使います。

コールバック関数の引数には各ドメインに定義されたresponseオブジェクトを渡します。このresponseオブジェクトは、最終的にSiriを介してユーザーに伝える情報を収容します。ユーザーに伝える情報量を増やすため、可能な限り多くの情報を詰め込んだ方がベターとされています。

Intentの処理に必要な情報を揃える

handlerオブジェクトは、SiriKitから呼び出されたとき、具体的な要求(Intent)の内容が収容されたIntentオブジェクトを元に処理を行うように実装する必要があります。

このとき、Intentに対する処理を行う上で必要な情報を受け取らなければなりませんが、Intentオブジェクトに収容されている情報が不十分である可能性があります。これは、ユーザーのSiriを使った音声での依頼の仕方に揺らぎが出るために起こり得ます。

そこでIntents Extensionでは、Intentに対する処理に必要な情報を揃えるために、リゾルブ / コンファーム / ハンドル という3つのフェーズを実装できるようになっています。この3つのフェーズの概要は、次の通りです。

  • リゾルブ : Intentの情報を調べ、対応する処理に必要な情報が揃っているか確認する
  • コンファーム : Intentの情報の最終的な検証を実施し、対応する処理が実行できる状態か確認する
  • ハンドル : Intentに対応する処理を実行する

リゾルブ

リゾルブフェーズでは、Intentオブジェクトに収容されているデータを確認し、必要な情報が揃っているか確認します。もし情報が欠如していた場合には、情報の追加をSiri経由でユーザーに依頼することができます。リゾルブフェーズを追加するには、各ドメインに定義されたIntentに対応するプロトコルのレゾルブメソッドを実装します。リゾルブフェーズの追加は任意であり、必要であれば実装します。例えば「配車を予約する」Intentの場合は次のように実装します。

func resolveDropOffLocation(forRequestRide intent: INRequestRideIntent, with completion: (INPlacemarkResolutionResult) -> Void) {
    let location = intent.dropOffLocation
    var result : INPlacemarkResolutionResult = nil
    
    if location != nil {
         // 適切な降車場所であればそれを採用する。そうでなければ、
         // そこには降車できない旨を通知する
        if self.locationIsInsideServiceArea(location) {
            result = INPlacemarkResolutionResult.successWithResolvedValue(location)
        } else {
            result = INPlacemarkResolutionResult.unsupported()
        }
    } else {
        // 降車場所を尋ねる
        result = INPlacemarkResolutionResult.needsValue()
    }
    completion(result)
}

リゾルブした結果はresolution resultオブジェクトというデータとして返します。resolution resultオブジェクトは、各ドメインでINIntentResolutionResultのサブクラスが定義されているため、該当するクラスをインスタンス化します。

結果として、次のような値を指定することができます。

  • 値をうまく整合できた。
  • 値は必要でなかった。
  • 曖昧さを解消する必要がある。
  • ユーザのコンファームが必要である。
  • 値が必要である(値が足りない)。
  • この値には対処できない。

コンファーム

コンファームフェーズでは、Intentオブジェクトに収容されているデータの最終確認を行い、Intentをハンドルできる状態になっているか確認します。リゾルブされた全ての情報がIntentオブジェクトに集約されるので、揃った情報を使って本当に処理の実行が可能かどうか検証する処理を加えることができます。

コンファームメソッドはリゾルブと同様、各プロトコルに定義されています。例えば「ワークアウトを開始する」Intentの場合は次のように実装します。

func confirmStartWorkout(startWorkout intent: INStartWorkoutIntent, completion: (INStartWorkoutIntentResponse) -> Void) {
    // Intentオブジェクトに収容されている情報を使って、処理が可能か検証する

    let response = INStartWorkoutIntentResponse(code: .ready, userActivity: nil)
    completion(response)
}

コンファームフェーズの追加は必ず必要というわけではありませんが、できるだけ実装した方が良いとされています。例えば「ユーザー間の送金」Intentでコンファームメソッドを実装すると、送金することの最終確認をSiriがユーザーに求めてくれるようになります。

語彙の追加

iOS アプリによっては、アプリ独自の用語やキーワードといったを語彙を使用している場合があります。その独自の語彙をSiriが解釈できるように設定できます。次の2つの方法のいずれかを使って、語彙を登録します。

  • INVocabularyオブジェクトを作る
  • 大域語彙ファイルを iOS アプリ自体に追加する

詳しくは公式ドキュメントを参照してください。

SiriKitを使ったiOSアプリの作りかたの基本

SiriKitを使ったiOSアプリの開発を始めるにあたり、まず試すべきはメッセージアプリへの組み込みです。公式のサンプルコードである「UnicornChat」を動かしてみるのが最も手軽です。またXcodeで生成できるIntents Extensionのテンプレートも「メッセージ」ドメインを用いた場合の実装コードになっているので、こちらで試してみるのもよいかと思います。

どちらも試してみた記事をDevelopers.IOで公開中ですので、参考にしながら試してみてください。

公式サンプルアプリ「UnicornChat」を動かしてみる

Siriからメッセージを送信できるアプリを作る (テンプレートの利用)

iOS 11からの新機能

本記事ではiOS 11からの新機能であるメモの作成QR コードの表示の2つのIntentを試してみます。

なお、iOS アプリのXcodeプロジェクトはSingle View Appのテンプレートで作成した状態から始めることとするため、事前に作成しておいてください。

Siriからメモを作成できるアプリを作る

まずはメモの作成に応答するだけのiOSアプリを作ってみましょう。メモの作成をSiri経由で行えるようにします。XcodeプロジェクトのProduct NameはVoiceMemoAppとします。

iOSアプリのTargetのCapabilityを設定する

まず、iOSアプリでSiriを利用できるようにするため、Capabilityを設定します。iOSアプリのTargetのCapabilityの設定にて Siri を有効にします。

SiriのCapabilityが設定された<アプリ名>.entitlementsが作られます。

ios-11-sirikit-01

Intents ExtensionをTargetに追加する

Xcodeプロジェクト作成後、まずはTargetにIntents Extensionを追加します。File > New > Targetを選び、一覧の中からIntents Extensionを選びます。

ios-11-sirikit-02

Product Name は Intents Extension の Bundle Identifier になります。ここではVoiceMemoAppExtensionとします。またInclude UI Extensionにチェックを入れるとIntents UI Extensionも一緒にTargetに追加できます。ここではIntents Extensionだけ試したいので、チェックは外しておきます。

ios-11-sirikit-03

追加したIntents ExtensionのSchemeを使えるようにするか問われます。デバッグに必要なのでActivateを選びます。

ios-11-sirikit-04

VoiceMemoAppExtensionというTargetが作られました。Groupの中にIntentHandler.swiftInfo.plistが作られます。

ios-11-sirikit-05

Intents Extension Targetの設定を変更する

Intents Extension生成時のTargetの設定および構成は「メッセージ」ドメインのIntents Extensionとして動作するテンプレートです。そのため、今回の例のように「メッセージ」ではないドメインのIntents Extensionを開発する場合、設定を修正する必要があります。

まずVoiceMemoAppExtensionInfo.plistを修正します。NSExtension > NSExtensionAttributes > IntentsSupportedを展開します。NSExtensionAttributesに記述すべき設定はApp Extensionの種類ごとに変わりますが、Intents Extensionの場合はIntentsSupportedおよびIntentsRestrictedWhileLockedを設定します。

IntentsSupportedはIntents Extensionが扱うIntentの種類(Siri 経由でどのようなことを指示できるようにするか)を設定します。IntentsRestrictedWhileLockedはデバイスのロックを解除する必要があるIntentの種類(例えば決済など)を設定します。

今回はIntentsSupportedの中にあるすべての項目を削除したあと、下記の項目を追加します。

  • INCreateNoteIntent(ノートに追加するIntent)

ios-11-sirikit-06

iOSアプリにSiriの利用許可の要求を実装する

次に、iOSアプリ側の設定に移ります。まず、iOSアプリでSiriと相互にやりとりを行うことができるようユーザーにSiriの利用許可を要求する設定および実装を行います。これは、プッシュ通知の許可や位置情報サービスの利用許可などといったユーザーに許可を要求する仕組みのSiri版といった感じです。

iOSアプリ側のTargetのInfo.plistNSSiriUsageDescriptionというキーを追加し、値に利用許可を要求する際に表示するテキストを記述します(NSSiriUsageDescriptionは入力後Privacy - Siri Usage Description という表記に変わります)。

ios-11-sirikit-07

次に、利用許可を要求する実装をビューコントローラーに加えます。実際の開発では要求する適切なタイミングはいつかよく考えて組み込む方が良いですが、今回はサンプルということでアプリ起動時に初めに表示されるViewControllerクラスに実装します。要求するにはINPreferencesクラスのクラスメソッドであるrequestSiriAuthorization(_:)を呼び出します。

    import UIKit
    import Intents
    
    class ViewController: UIViewController {
        override func viewDidLoad() {
            super.viewDidLoad()
            INPreferences.requestSiriAuthorization { status in
                print("status = \(status.rawValue)")
            }
        }
    }

iOSアプリを実行すると、Siriの利用許可を要求するアラートが表示されるようになります。なお、このアラートが表示されるのは初回のみです(一度キャンセルした場合は設定から有効化できる)。

ios-11-sirikit-08

IntentsHandlerに処理を実装する

次にIntents Extension側の実装に移ります。自動生成されたIntentsHandlerクラスには、「メッセージ」ドメインの Intents Extensionとして動作するテンプレートコード(INSendMessageIntentHandling プロトコルやINSearchForMessagesIntentHandlingプロトコルの実装コード)が実装されています。これらの実装コードは使わないので、まずは「メモを作成する」というIntentに必要な実装コードに書き換えます。

    import Intents
    
    class IntentHandler: INExtension {
        override func handler(for intent: INIntent) -> Any {
            return self
        }    
    }
    
    extension IntentHandler: INCreateNoteIntentHandling {
        public func handle(intent: INCreateNoteIntent, completion: @escaping (INCreateNoteIntentResponse) -> Swift.Void) {
        
            // 本来であれば、保存処理などを行う (本記事では割愛)
        
            let response = INCreateNoteIntentResponse(code: INCreateNoteIntentResponseCode.success, userActivity: nil)
            response.createdNote = INNote(title: intent.title!, contents: [intent.content!], groupName: nil, createdDateComponents: nil, modifiedDateComponents: nil, identifier: nil)
            completion(response)
        }
    }

「ノートを作成する」Intentを作る場合は、IntentHandlerクラスにINCreateNoteIntentHandlingプロトコルに準拠させる必要があります。このように、ある種類のIntentクラスに対してIN~IntentHandlingプロトコルがペアとなって定義されているのが基本です。

INCreateNoteIntentHandlingプロトコルに準拠させるにはhandle(intent:completion:)メソッドの実装が必須となっています。このメソッドはSiriKitから要求が来ると呼び出され、第一引数のintentにどのような要求が行われたか詳細なデータが入ってきます。それに応じた処理を実行したあとにcompletionハンドラを呼び出すことで、SiriKitを経由してユーザーにレスポンスを返すことができます。このハンドラは引数としてINCreateNoteIntentResponseインスタンスを必要としていますので、生成して返します。

上記のコード例では新しく生成したINCreateNoteIntentResponseインスタンスに、引数として渡されたintentに入っている情報をセットし、completionハンドラを呼んでいます。実際にメモアプリを開発する際は、この前のタイミングで保存処理を実装します。

デバッグ実行し、Siriから呼び出す

デバッグ実行は他のApp Extensionと同様、Intents ExtensionのSchemeを選択して実行します。ビルドが終わると iOS上でSiriが立ち上がります。なお、この際に実行対象はiOSデバイスの実機でもiOSシミュレータでも、どちらからでも試すことができます(iOSシミュレータの場合はMacのスピーカーに話しかける)。

ios-11-sirikit-09

例えば「VoiceMemoAppで休暇というメモを作ってモントリオール大好きと書いて」と依頼すると、作成されたメモの結果が表示されます。

ios-11-sirikit-10

Siriに問いかけてみるとよく分かりますが、呼びやすいアプリ名の方が明らかに好ましいです。アプリ名が長かったり、複数の呼び方があったりすると、音声での指示がしづらいです。

「メモ」ドメインでは他にも「既存のメモに追加する」や「メモを検索する」、「タスクを作成する」などといったIntentが提供されています。詳しくは下記をご覧ください。

SiriからQRコードを表示できるアプリを作る

続いて、QRコードを表示するiOSアプリを作ってみましょう。SiriからQRコードを要求されたときに、QRコードをSiriに表示できるようにします。XcodeプロジェクトのProduct NameはQRAppとします。iOSアプリプロジェクトの作成とSiriKitのための設定、Intents Extension Targetの追加の手順は、前述しているメモアプリと同様のため割愛します。

QRコードを用意する

Intentを使って表示することができるQRコードは、原則として保存済みの画像ファイルまたはインターネットからダウンロードできる画像ファイルのようです。そのため本記事では、QRコード画像ファイルをあらかじめ作成し、Xcodeプロジェクトに追加しておく必要があります。今回はqr.pngというQRコード画像ファイルを作成しました。

QRコードの画像ファイルの生成はこちらを利用させていただきました。

ドメインを設定する

Intents ExtensionのTargetのinfo.plistに、「QRコードを作成する」Intentを設定します。NSExtension > NSExtensionAttributes > IntentsSupportedを展開し、中のアイテムを下記に変更します。

  • INGetVisualCodeIntent (QR コードの取得)

ios-11-sirikit-11

IntentHandlerクラスを実装する

IntentHandlerクラスには「メッセージ」ドメインのための実装が記述されているので、削除したのち以下のコードに書き換えます。

    import Intents
    import CoreImage
    
    class IntentHandler: INExtension {
        override func handler(for intent: INIntent) -> Any {
            return self
        }
    }
    
    extension IntentHandler: INVisualCodeDomainHandling {
        public func handle(intent: INGetVisualCodeIntent, completion: @escaping (INGetVisualCodeIntentResponse) -> Void) {
            let response = INGetVisualCodeIntentResponse(code: .success, userActivity: nil)
            response.visualCodeImage = INImage(named: "qr.png")
            completion(response)
        }
    }

デバッグ実行し、Siriから呼び出す

以上で実装は終わりです。Intents ExtensionのSchemeをデバッグ実行し、Siriを立ち上げます。「QRAppでブログのバーコードを表示して」と依頼すると、QRコードが表示されます。

ios-11-sirikit-12

QRコードを表示するIntentの開発方法についての情報は、公式ドキュメントとして公開されています。こちらもあわせてご覧ください。

SiriKitのデザインガイドライン

SiriおよびSiriKitを用いたユーザーインターフェースについて、iOS Human Interface Guidelinesで解説されています。開発の前に一読しておくと、Siriによりマッチした一連のエクスペリエンスが提供できると思います。

まとめ

音声からiOSアプリを操作することができるSiriKitは、既存のiOSアプリに新しいユーザーインターフェースを提供することができる、素晴らしい機能です。

Siri自体はiPhone 4Sに初めて搭載されて以来、iOS のバージョンアップにともなって進化を繰り返し、非常に有能なパーソナルアシスタントに成長しました。声による操作自体も今となってはかなり普及しており、若者を中心に使われ始めています。

音声インターフェースの特に重要なポイントは必要なときだけ使われるというところです。あるときは音声での指示の方が簡単になる場面もありますが、あるときはアプリを起動して操作したほうが早くタスクをこなせる場面もあります。あくまでiOSアプリの拡張ですので、SiriKitを使った音声インターフェースはiOSアプリの機能を使うためのショートカットとして提供するという認識が大事だと思います。

参考記事

この記事の執筆にあたり、下記の情報を参考にいたしました。