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


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

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

導入

Xcodeでの準備

  1. XcodeでProjectを開く
  2. メニューバーのFile->New->Targetを選択
  3. Notification Service Extensionを選択
  4. Nameや導入するターゲットを選択してFinish
  5. Targetsの中にExtensionが入ります(ここでは名前をextensionという名前にしています)
  6. Projectツリーの部分にもファイルが追加されています

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と合わせないといけない
ということでした。

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

終わりです

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

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