Firebaseを使ったiOSのPush通知に画像を添付する Notification Service Extension

iOSではPush通知に画像を添付するために一手間かけないといけません。
それがNotification Service Extensionの導入です。

Firebaseを使いましたが、もちろんFirebaseを使わないPush通知にも応用可能なので、是非参考にしていただければと思います。

#導入
###Xcodeでの準備

1. XcodeでProjectを開く
2. メニューバーのFile->New->Targetを選択
<img width="533" alt="スクリーンショット 2020-08-04 10.08.59.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/678249/3db61f1f-a3b3-c2e4-fd8f-6d258f1b5fe3.png">
3. Notification Service Extensionを選択
<img width="717" alt="スクリーンショット 2020-08-04 10.09.34.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/678249/ee08688d-466f-55de-3b65-02480beb9110.png">
4. Nameや導入するターゲットを選択してFinish
5. Targetsの中にExtensionが入ります(ここでは名前をextensionという名前にしています)  
<img width="166" alt="スクリーンショット 2020-08-04 10.12.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/678249/f9ac6b40-d0d8-57dc-b64c-95c8919c8627.png">
6. Projectツリーの部分にもファイルが追加されています

<img width="218" alt="スクリーンショット 2020-08-04 10.14.41.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/678249/e25283cd-04ba-9190-d232-cc04e4b2bdcf.png">

###Developerサイトでの準備
Notification Service Extensionを利用するにはこのターゲット用のApplication IDとProvisioning Profileが必要になります。
アプリ用のものとは別のものを用意しないといけません。

1. DeveloperサイトにてCertificates, Identifiers & ProfilesでApplication Identifierを追加
※**ここで大事な点があります**
例えばアプリ用に用意しているBundle Identifierが
**com.sample.application**
だったとするならば
NotificationExtension用のBundle Identifierは
**com.sample.application**.extension
のように上位部分を合わせてください

2. Provisioning Profileを作成
アプリ用のものと全く同じような作成方法で問題ありません

###Firebase側で気を付ける点


```
curl -X POST --header "Authorization: key=*******************" \
--Header "Content-Type: application/json" \
https://fcm.googleapis.com/fcm/send \
-d @- << EOF
{
    "registration_ids": [
"********************************"
    ],

    "notification": {
        "title": "タイトル",
        "imageUrl": "https://sample.*******.jpg"
    },
    "priority":10,
    "mutable_content":true
}
EOF
```

送信時のコマンドはこのような感じになります
注意点としては
**"mutable_content":true**
こちらの書き方です
Firebaseのサポートサイトでは
mutable-content:1のような記述がありますが、上記の方法で送信することによってiOSの通知に変換される時にはmutable-content:1がちゃんと入っています。
これが入っていないと全く動きません。

###コーディング
自動で生成されたNotificationService.swiftを編集します。

```NotificationService.swift
    var contentHandler: ((UNNotificationContent) -> Void)?
    var bestAttemptContent: UNMutableNotificationContent?

    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
        self.contentHandler = contentHandler
        bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)        
        if let urlString = request.content.userInfo["gcm.notification.imageUrl"] as? String,
            let  fileURL = URL(string: urlString) {
            URLSession.shared.downloadTask(with: fileURL) { (location, response, error) in
                if let location = location {
                    // メディアファイルをダウンロードしてtmpに保存
                    let tmpFile = "file://".appending(NSTemporaryDirectory()).appending(fileURL.lastPathComponent)
                    let tmpUrl = URL(string: tmpFile)!
                    try? FileManager.default.moveItem(at: location, to: tmpUrl)
                    
                    if let attachment = try? UNNotificationAttachment(identifier: "hoge", url: tmpUrl, options: nil) {
                        // メディアを添付
                        self.bestAttemptContent?.attachments = [attachment]
                    }
                }
                contentHandler(self.bestAttemptContent!)
            }.resume()
        }
    }

```

上記を用意しておけばOKです。
処理の流れとしては
Push通知が届くとdidReceiveが走り、メッセージの中にある画像URLをダウンロードして通知内容に添付します。
Extensionという名のように、通知の内容を拡張して書き換える、といったイメージです。
画像URLのリンクが切れていたりダウンロードに失敗してもcontentHandlerを実行するまでの時間制限が存在するのでPush通知自体が全く発生しなくなるといった心配はありません。


#嵌りポイント

私が何度やってもどうしてもうまくいかなかった嵌りポイントだけ紹介させて下さい。
それが、Extension用Targetの対応OSバージョンをアプリターゲットのOSと合わせないといけない
ということでした。

<img width="695" alt="スクリーンショット 2020-08-04 11.01.45.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/678249/a6f75616-fb77-7bfb-e478-b8369fa79fc4.png">

最初からextensionを用意していればこんなことにはならなかったのですが、アプリ用ターゲットの作成からextension作成までの時期に半年程の差があったので作成時の初期設定されているバージョンがずれてしまっていたというのが原因でした。


###終わりです

読んでいただきありがとうございました。

Brewus,Inc.
株式会社ブリューアス
https://brewus.co.jp