INRestaurantReservation: ユーザーの予約情報をSiriに共有して、イベントを自動的に提案する

あなたがレストラン予約アプリケーション、またはユーザーのために予約を入れるアプリケーション（フライト、列車、チケット）を開発している場合、予約の詳細についてSiriに通知することができます。

この記事では、予約アプリのデモを作成します。ユーザーが予約を行うと、アプリが予約情報をSiriに送信し、Siriは予約情報をカレンダーに追加するようユーザーに促します。予約が閲覧されると、アプリが予約時間を更新します。システムカレンダーアプリが予約情報の更新について、ユーザーに通知するのが確認できます。

## 利点

Siriに通知することで、ユーザーはイベントをカレンダーに追加するための通知を即座に受け取ります。

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/3c405b47-a368-3008-9e0a-75983c17f121.gif">

イベント会場の詳細情報もSiriによって自動的に入力されます。

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/0d92aa6f-805f-d04a-e052-930b8bac6785.png">

また、Siriは予約前に関連情報をユーザーの画面に表示します。

もう一つのメリットは、（予約IDに基づいて）特定の予約イベントに関する最新情報がアプリ上で更新されると、Siriがイベントの詳細を自動的に更新することです。アプリ上で既存の予約に変更があると、システムカレンダーアプリに通知バッジが表示されます。

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/bdf39d9d-deed-6716-5f12-825071332df1.jpeg">

`import intents`

Github: [https://github.com/mszmagic/SiriReservationSample](https://github.com/mszmagic/SiriReservationSample)

## ステップ 1. 期間を定める

まず、予約が続行する期間を定める関数を書きます。実演アプリケーションでは、レストランの予約を2時間に設定しました。

```swift
private func getReservationPromoteDateRange(_ item: Reservation) -> INDateComponentsRange {
    let calendar = Calendar.autoupdatingCurrent
    // 予約時間の2時間後まで、予約の詳細を閲覧するようユーザーに宣伝しましょう
    let promoteDate_end = calendar.date(byAdding: .hour, value: 2, to: item.bookedTime) ?? item.bookedTime
    let promoteDate_end_components = calendar.dateComponents(in: TimeZone.autoupdatingCurrent, from: promoteDate_end)
    let promoteDate_start = item.bookedTime
    let promoteDate_start_components = calendar.dateComponents(in: TimeZone.autoupdatingCurrent, from: promoteDate_start)
    //
    return .init(start: promoteDate_start_components, end: promoteDate_end_components)
}
```

## ステップ 2. 予約名を定める

予約項目に対して固有の識別名を作成する必要があります。

```swift
let reservation_item = INSpeakableString(vocabularyIdentifier: item.reservationID, spokenPhrase: "\(item.restaurantName)の予約", pronunciationHint: nil)
```

## ステップ 3. 予約を見る時に実行するアクションを定める

それから、ユーザーが予約を見たい時に実行するアクションを定めます。URLを提供することもできます。これらの情報は、ユーザーが特定の予約項目を見たい時に、どのようなアクションを実行するかをSiriが認識するために使用されます：

```swift
private func getViewReservationDetailsAction(_ item: Reservation) -> INReservationAction {
    let viewDetailsAction = NSUserActivity(activityType: "com.example.SiriReservationSample.viewReservationDetails")
    let reservationDateStr = DateFormatter.localizedString(from: item.bookedTime, dateStyle: .short, timeStyle: .short)
    viewDetailsAction.title = "\(item.restaurantName)における\(reservationDateStr)での予約状況の詳細を表示します"
    viewDetailsAction.userInfo = ["reservationID" : item.reservationID]
    viewDetailsAction.requiredUserInfoKeys = ["reservationID"]
    viewDetailsAction.webpageURL = generateReservationURL(item)
    return .init(type: .checkIn,
                 validDuration: getReservationPromoteDateRange(item),
                 userActivity: viewDetailsAction)
}
```

## ステップ4. 予約オブジェクトを決めてください

```swift
let reservation = INRestaurantReservation(itemReference: reservation_item,
                                          reservationNumber: item.reservationID,
                                          bookingTime: item.bookedTime,
                                          reservationStatus: item.reservation_status,
                                          reservationHolderName: item.personName,
                                          actions: reservation_actions,
                                          url: generateReservationURL(item),
                                          reservationDuration: getReservationPromoteDateRange(item),
                                          partySize: item.reservation_partySize,
                                          restaurantLocation: item.restruantLocation)
```

| Variable name | Explanation |
|---|---|
| itemReference | 参照IDオブジェクト `reservation_item ` |
| reservationNumber | 予約ID |
| bookingTime | ユーザーがこの予約を入れる時間 |
| reservationStatus | 予約状況 |
| reservationHolderName | この予約を行なった人物の名前 |
| actions | この例における関数 `getViewReservationDetailsAction()` のアウトプット |
| url | ユーザーが予約詳細へアクセスするためのURLアドレス（ウェブページ） |
| reservationDuration | 予約の開始と終了を定めてください。`getReservationPromoteDateRange()` 機能の出力 |
| partySize | この予約の対象人数 |
| restaurantLocation | レストランの所在地 `CLPlacemark`|

## ステップ５．予約についてシステムに入力してください。

ここで、予約についてシステムに入力する必要があります。

```swift
let intent = INGetReservationDetailsIntent(reservationContainerReference: reservation_item, reservationItemReferences: nil)
let response = INGetReservationDetailsIntentResponse(code: .success, userActivity: nil)
response.reservations = [reservation]
let interaction = INInteraction(intent: intent, response: response)
interaction.donate(completion: completionHandler)
```

これで、カレンダーのイベントに追加するためのSiriのメッセージがユーザーに表示されます。

### 関数を呼び出すタイミング

ユーザーが予約を閲覧するたびに（情報が変更されていなくても）上記の関数を呼び出す必要があります。予約の詳細が変更された場合は、Siriがカレンダーアプリを通じてユーザーに通知します。

なお、1つの予約に対してご自分の予約IDが同じであることを確認することが重要です。

## ステップ6. NSUserActivityTypes

プロジェクトの設定から `Info` タブに移動し、サポートされているアクティビティタイプの情報を追加します。上のコードでは、アクティビティタイプとして `com.example.SiriReservationSample.viewReservationDetails` を使用しています。

<img width="600" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/8ea53fc6-4b65-6caf-1d08-ef8f1a1c47d6.png">

## ステップ 7. Siriでのアプリケーションの起動を処理

ユーザーが予約の閲覧を希望する場合、Siriでアプリケーションを起動する事ができます。

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/a6427b7e-850f-fbc5-bfda-5be33c464d66.gif">

`SceneDelegate.swift` ファイル内に次の関数を追加してください：

```swift
func scene(_ scene: UIScene, continue userActivity: NSUserActivity) {
    if userActivity.activityType == "INGetReservationDetailsIntent" {
        //
    }
}
```

こちらが予約IDをフェッチする方法です：

```swift
if let userActivity = notificationObject.object as? NSUserActivity,
   let intentObject = userActivity.interaction?.intent as? INGetReservationDetailsIntent,
   let reservationName = intentObject.reservationItemReferences?.first,
   let reservationID = reservationName.vocabularyIdentifier {
    DispatchQueue.main.async {
        self.viewingReservationID = .init(reservationID: reservationID)
    }
}
```

`userInfo` プロパティの利用を試みましたが、そこには、予約IDは含まれていないようです。つきましては、代わりに上記のコードを使って予約IDを取得してください

以前のコードの設定：

```swift
let reservation_item = INSpeakableString(vocabularyIdentifier: item.reservationID, spokenPhrase: "\(item.restaurantName)の予約", pronunciationHint: nil)
```

従って、変数 `vocabularyIdentifier` にアクセスすると、`item.reservationID` が得られます。

---
:relaxed: [Twitter @MszPro](https://twitter.com/MszPro)

:sunny: [私の公開されているQiita記事のリストをカテゴリー別にご覧いただけます。](http://mszpro.com/ioskiji/)
<img width="150" src="https://cdn.mszmagic.com/static-web-content/AppClipImage_small.png">