追加要件：FCMにて画像付きPush通知を送る iOS10以上


# 前置き
- FCMコンソールからのPush通知が実装済みからの追加要件という内容です。
- 今回の記事では送信方法としてPostmanを使用しております。
- なるべく細かく書くつもりですが、後半になるにつれ適当になる可能性があります。

# 概要
iOS10から追加された **Notification Service Extension** を利用する。
これは「送信されたペイロード」 -> 「端末に表示」　までの間の部分をあれこれ出来る。
そのため表示される前に画像を読み込んで、それを表示させるといった流れを行える。

# 目次
- Notification Service Extension追加
- Notification Service Extension用のProvisioningファイルを用意
- Notification Service Extension編集
- 送信

## Notification Service Extension追加
1. XcodeメニューバーのFile -> New -> Target
1. `Notification Service Extension` を選択 -> Next
<img width="715" alt="スクリーンショット 2020-06-03 11.19.05.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185455/25beed83-2bc6-ae13-429d-8a40402d3753.png">

1. Product Nameを入力(ex: NotificationService) -> Finish
> 「MainProjectのBundleIdentifier.上記ProductName」という形式のBundleIdentifierとなる。
> 言わずともこれは後述するProvisioningファイルのBundle IDとなる。

1. ダイアログが出たらActivateを選択
1. `NotificationServiceExtension`ディレクトリ配下に`NotificationService.swift`と`Info.plist`があるものが追加されていればOK
`NotificationService.swift`には下記２つのメソッドが既存で作成されている。

```swift:NotificationService
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
    ///省略
}
override func serviceExtensionTimeWillExpire() {
    ///省略
}
```

## Notification Service Extension用のProvisioningファイルを用意
MainAppと異なる`BundleIdentifier`をもつTargetが追加されたため、
これに対してProvisioningファイルを用意する必要がある。

1. AppleDeveloper SignIn
1. `Certificates, Identifiers & Profiles` -> `Identifiers` -> 「+」押下
<img width="655" alt="スクリーンショット 2020-06-03 10.59.43.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185455/4a819d8d-a1bd-8db5-6aed-f1fd61c5c0c1.png">

1. App IDs 選択 -> Continue 
1. 各項目入力 -> 作成
> Description: NotificationServiceExtension
> Bundle ID: 「MainProjectのBundleIdentifier.先程のProductName」
※コピーするとエラーが出ることがあるようで、その場合は手動入力をする必要がある。
1. `Certificates, Identifiers & Profiles` -> `Profiles` -> 「+」押下
1. `App Store` -> 先ほど作成したApp IDs選択 -> メインと同じ`Certificate`ファイルで作成
1. 作成すると既存Provisioningファイルが無効になるため Edit -> Save をし更新する。
1. xcodeに戻る。
1. XcodeメニューバーのXcode -> Preferences -> Download Manual Profiles より先ほど作成したProvisioningファイルをDL
1. 各TARGETSに適切なProvisioningProfileを選択
<img width="212" alt="スクリーンショット 2020-06-03 12.11.27.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185455/ee1d7b3f-2297-5591-d1f6-96b4d071d04b.png">

1. MainAppの`Signing & Capabilities`にBackground　Modesを追加
<img width="790" alt="スクリーンショット 2020-06-03 12.13.42.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185455/a89fb32d-e0f0-2cc8-b44a-692c5bd18894.png">


## Notification Service Extension編集

```swift:NotificationService.swift
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
    
    self.contentHandler = contentHandler
    bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
    
    self.realmWrite(request: request, bestAttemptContent: bestAttemptContent)
    
    if let imageUrl = request.content.userInfo["image_url"] as? String {
        let session = URLSession(configuration: URLSessionConfiguration.default)
        let task = session.dataTask(with: URL(string: imageUrl)!, completionHandler: {[weak self](data, response, error) in
            do {
                if let writePath = NSURL(fileURLWithPath:NSTemporaryDirectory())
                    .appendingPathComponent("tmp.jpg") {
                    try data?.write(to: writePath)
                    let identifier = "hogehoge"

                    if let bestAttemptContent = self?.bestAttemptContent {
                        let attachment = try UNNotificationAttachment(identifier: identifier, url: writePath, options: nil)
                        bestAttemptContent.attachments = [attachment]
                        contentHandler(bestAttemptContent)
                    }
                } else {
                    if let bestAttemptContent = self?.bestAttemptContent {
                        contentHandler(bestAttemptContent)
                    }
                }
            } catch let error as NSError {
                print(error.localizedDescription)

                if let bestAttemptContent = self?.bestAttemptContent {
                    contentHandler(bestAttemptContent)
                }
            }
        })
        task.resume()
    } else {
        if let bestAttemptContent = self.bestAttemptContent {
            contentHandler(bestAttemptContent)
        }
    }
}

override func serviceExtensionTimeWillExpire() {
    if let contentHandler = contentHandler, let bestAttemptContent =  bestAttemptContent {
        // 実行時間をすぎた場合の救済
        contentHandler(bestAttemptContent)
    }
}
```

## 送信
### 送り先
POST: https://fcm.googleapis.com/fcm/send

### Header
```json:Header
Content-Type:application/json
Authorization:key=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx  
```
<img width="637" alt="スクリーンショット 2020-06-03 12.24.22.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185455/44ea088a-d803-14e0-5c8b-0b44fc8d73db.png">

1. Firebase
1. 歯車アイコン -> プロジェクト設定 -> CloudMessaging 
<img width="229" alt="スクリーンショット 2020-06-03 12.27.11.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185455/9210f13d-a0d0-fd6a-7167-52555bc9291a.png">

1. サーバーキーのトークンをコピー
<img width="997" alt="スクリーンショット 2020-06-03 12.31.17.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185455/f6e93062-03c2-b655-a45b-8088de8060c0.png">

1. 「xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx」をコピーしたトークンに変更






### Body
```json:Body
{
    "data" : {
        "image_url":"https://hogehoge.jp/fugafuga.png",
    	"type" : "1"
    },
    "notification" : {
    	"title": "タイトル",
        "body": "ボディ"
	},
	"mutable_content": true,
	"content_available": true,
    "to" : "token or topic"
}
```

<img width="850" alt="スクリーンショット 2020-06-03 12.24.55.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185455/54651837-d2cc-9112-b2a7-d828bf912d0b.png">

#### Key:toに指定するもの

1. 少しAppDelegateに追加 

```swift:AppDelegate.swift
extension AppDelegate: MessagingDelegate {
    func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String) {
        print("fcmToken:\(fcmToken)")
        Messaging.messaging().subscribe(toTopic: "/topics/global") { error in
            if let error = error {
                print("TopicErrorRegistError: \(error)")
            }
        }
    }
}
```

この状態でビルドし、コンソールに表示されたものを利用。

- デバイス単体に送りたい場合は`fcmToken:xxxxxxxxxxxxxxxxx`の`xxxxxxxxxxxxxxxxx`部分
- topic購読している全デバイスに送りたい場合は`/topics/global`

> toTopicはお好きなもので。


いざ！送信！！
届かなかった方はコメントください。

## 追記
### Debug
`NotificationService`がきちんと走っている？？
と気になりDebugで止めたい場合はMainAppをビルドし一旦メインをデバイスに流した後、`NotificationService`を指定してビルドすることで可能

<img width="270" alt="スクリーンショット 2020-06-03 12.52.35.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/185455/9ff45019-c2ee-945e-181d-74b68d3f12ce.png">

### 脱線
今回の内容と少し違いますが
`AppGroups`を利用することでMainとPushに共通データを持つことが可能。
例えば通知を見逃したユーザのために、通知履歴などをアプリに実装。

~流れ~
1. 通知を送る
2. `NotificationService.swift`にてRealm等に保存処理(SharingPath)
3. `MainApp`にてSharingPathを元に`通知App`にて保存したデータを共有Realmから読み取る
4. データを元に履歴List画面作成

jsonの`data`に任意の値(この記事でいうtype)を追加するなどすれば、履歴リストからタップし、画面遷移なども行えますね！

需要がありそうなら細かく書きますが、、
こんなこともできますよーという話でした。