FirebaseでiOSのプッシュ通知を実装

#始めに
iOS アプリでプッシュ通知機能を実装しようと思ったら、想定より苦戦したので、振り返ってみようと思います。
#Firebase プロジェクトを作成する
[Firebase コンソール](https://console.firebase.google.com/?hl=ja "Firebase コンソール")で [プロジェクトを追加] をクリックし、 [プロジェクト名] を選択して、新しいプロジェクト名を入力します。
#アプリを Firebase に登録する
1. Firebase プロジェクトを作成したら、プロジェクトに iOS アプリを追加できます。
[Firebase コンソール](https://console.firebase.google.com/?hl=ja "Firebase コンソール")から、 [プロジェクトページ] に移動して、中央にある iOS アイコンをクリックして設定ワークフローを起動します。
2. アプリのバンドル ID を [iOS バンドル ID] フィールドに入力します。このバンドル ID を探すには、 XCode でアプリを開き、最上位のディレクトリの [General] タブにアクセスします。[bundle identifier] フィールドの値が iOS バンドル ID です（例: com.atsushi-uchida.NewsTwit）。

#Firebase 構成ファイルを追加する
1. [Download GoogleService-Info.plist] をクリックして、Firebase iOS 構成ファイル（GoogleService-Info.plist）を取得します。
2. 構成ファイルを Xcode プロジェクトのルートに移動します。メッセージが表示されたら、構成ファイルをすべてのターゲットに追加するオプションを選択します。

#アプリに Firebase SDK を追加する
Firebase ライブラリのインストールには[CocoaPods](https://guides.cocoapods.org/using/getting-started.html#getting-started "CocoaPods")を使用することをおすすめします。
・ Podfile がない場合は作成します。

```
$ cd your-project-directory
```
```
$ pod init
```
・ アプリで使用したいポッドを Podfile に追加します。たとえば、アナリティクスの場合は次のようになります。

```
$ pod 'Firebase/Analytics'
```
これにより、iOS アプリで Firebase を稼働させるために必要なライブラリが、Firebase 向け Google アナリティクスとともに追加されます。

・ ポッドをインストールし、.xcworkspace ファイルを開いて Xcode でプロジェクトを確認します。

```
$ pod install
```
```
$ open your-project.xcworkspace
```
・ 注意！！！一度でも、CocoaPods でポッドをインストールをした事がある場合は、インストールではアップデートにします。

```
$ pod update
```
2回目以降も、インストールを使用すると、依存性でエラーになる事が多いです。
#証明書を発行編に移ります
プッシュ通知を送るために作成するもの・しておくことは以下の７つあります。

1. CSRファイルの作成  **※初回作成すれば後は同じものを使いますので改めて作成不要です**
2. 開発用証明書(.cer)の作成   **※初回作成すれば後は同じものを使いますので改めて作成不要です**
3. AppIDの作成
4. 端末の登録
5. プロビショニングプロファイルの作成
6. APNs用証明書(.cer)の作成
7. APNs用証明書(.p12)の作成

#1. CSRファイルの作成
1. キーチェーンアクセスを開きます
2. 「キーチェーンアクセス」＞「証明書アシスタント」＞「認証局に証明書を要求」をクリックします
3. 「ユーザーのメールアドレス」を入力します
4. （「通称」はそのまま、「CAのメールアドレス」は空欄でOK）
5. 「要求の処理」は「ディスクに保存」を選択し「鍵ペア情報を設定」にチェックを入れます
6. 「続ける」をクリックします
7. 保存先の選択が出るので任意の場所を選択し「保存」をクリックします
8. 「鍵ペア情報」画面を確認して「続ける」をクリックします
9. 「設定結果」画面が出るので「完了」をクリックします

#2. 開発用証明書(.cer)の作成
1. [Apple Developer Program](https://developer.apple.com/account/ "Apple Developer Program")にログインします
2. 「Certificates, Identifiers & Profiles」をクリックします
3. 「Certificates」をクリックし、「Certificates」の隣にある「+」をクリックします
4. 「Create a New Certificate」画面が表示されるので設定していきます
5. 「iOS App Development」にチェックをいれ、右上の方の「Continue」をクリックします
6. 「Choose File」をクリックして、1.で作成した「CSRファイルの作成」を選択し、「Continue」をクリックします
7. 開発者用証明書が作成されるので、「Download」をクリックして書き出しておきます
8. 開発用証明書(.cer)の作成は完了です

#3. App ID の作成
1. 2.　「開発用証明書(.cer)の作成」の作業がスキップだった場合は、Apple Developer Programにログインして、「Certificates, Identifiers & Profiles」をクリックします（※2.の画像参照）
2. 「Identifiers」の右の「＋」をクリックします
3. 「Register a New Identifier」が表示されるので「App IDs」にチェックをいれ、右上の方の「Continue」をクリックします
4. 「Description」にアプリの概要を記入します 例） TestPushApp
5. 「Bundle ID」では「Explicit」を選択し、「Bundle ID」を入力します
6「Wildcard」を選択するとプッシュ通知が利用できないので注意！
7. 「Bundle ID」は アプリ側で同じものを設定します ので必ず控えておきましょう！
8. 下にスクロールして「Capabilities」の「Push Notifications」にチェックを入れます
9. これを忘れるとプッシュ通知が利用できないので注意！
10. 「Continue」をクリックします
11. 確認画面が表示されるので「Push Notifications」にチェックが入っている事を確認して「Register」をクリックします
12. これでApp ID 作成は完了です

#4. 端末の登録
1. 「Devices」をクリックして、「Devices」の隣にある「＋」をクリックします
2. 「Platform」は「iOS, tvOS, WatchOS」に選択します
3. 端末の「Device Name」と「Device ID(UDID)」を入力します
4. 「Device Name」は自由に設定できます
5. 「Device ID(UDID)」はXcodeを使うと確認し易いです
6. Mac に端末を接続し、Xcodeを起動します
7. 「Window」＞「Devices and Simulators」をクリックします
8. 「identifier」としてUDIDが確認できます
9. 記入できたら「Continue」をクリックします
10. 次の画面で端末情報を確認して「Register」をクリックします
11. これで端末登録は完了です

#5. プロビショニングプロファイルの作成
1. 「Profiles」をクリックして、「Profiles」の隣にある「＋」をクリックします
2. 「Development」の「iOS App Development」を選択し、「Continue」をクリックします
3. 利用する App ID、開発用証明書、端末をそれぞれ紐付けていきます
4. 3.「App ID の作成」で作成したApp IDを選択し、「Continue」をクリックします
5. 2.「開発用証明書(.cer)の作成」で作成した（あるいは既存の） 開発用証明書 を選択し、「Continue」をクリックします
6. 4.「端末の登録」で登録した（あるいは既存の）端末を選択し、「Continue」をクリックします
7. 最後に「Provisioning Profile Name」にファイル名入力します 例） TestPushApp Provisioning Profiles
8. 紐付けを確認し「Generate」をクリックします
9. プロビショニングプロファイルがが作成されるので、「Download」をクリックして書き出しておきます
10. プロビショニングプロファイルの作成は完了です

#6. APNs用証明書(.cer)の作成
1. 「Certificates」をクリックして、「Certificates」の隣にある「＋」をクリックします
2. 2.「開発用証明書(.cer)の作成」で開発用証明書を作成したときとは異なり、下にスクロールし、「Service」の「Apple Push Notification service SSL (Sandbox)」をチェックに入れます
3. 「Continue」をクリックします
4. 3.「App ID の作成」で作成した App ID を選択し、「Continue」をクリックします
5. 1.「CSRファイルの作成」で作成した（あるいは既存の）CSRファイルを選択し、「Continue」をクリックします
6. APNs用証明書(.cer) が作成されるので、「Download」をクリックして書き出しておきます
7. APNs用証明書(.cer) の作成は完了です

#7. APNs用証明書(.p12) の作成
1. 6.「APNs用証明書(.cer)の作成」で作成した「APNs用証明書(.cer)」をダブルクリックしてキーチェーンアクセスを開きます
2. APNs用証明書(.cer) の左にある三角マークをクリックして開きます
3. APNs用証明書(.cer) ファイルには鍵がセットされています
4. APNs用証明書(.p12) を書き出すには、開いた状態で 鍵ではなく証明書の上で右クリック をして「～を書き出す…」をクリックします
5. ファイル名「名前」と保存先「場所」を指定して「保存」をクリックします
6. パスワードを求められますが、 何も入力しない で「OK」をクリックします
7. この後、システム側にパスワードを求められる場合があります。対応してください。
8. APNs用証明書(.p12) が書き出されます
9. APNs用証明書(.p12) 作成は完了です
10. これですべて必要なファイルが作成できました

#FirebaseのCloud MessagingにAPNs証明書を登録
1. 次にFirebaseの設定画面に移動します。
2. Firebase Consoleの対象のプロジェクトの画面左上部の歯車のアイコンを選択して、プロジェクトの設定を選択します。
3. SettingメニューのCloud Messagingを選択します。
4. 画面スクロールしIOSアプリの設定のAPNs証明書の開発用APNs証明書のアプロードを選択します。
5. ファイルをアップロードをクリックし、7. 「APNs用証明書(.p12) の作成」で作成したAPNs証明書を選択してップロードを選択します。
6. こちらでアップロードが完了すればFirebaseの設定は完了です。

#XcodeのSigningを設定
1. 次にXcodeの設定を行います。Xcodeのこちらの画面を開き、Signing & Capabilityを選択します。
2. まずAutomatically manage signingのチェックを外し、Provisioning Profileに5. 「プロビショニングプロファイルの作成」で作成したProvisioning Profileを設定します。
3. 次に+CapabilityをクリックしてPush NortificationsをSigningのした後にドラッグアンドドロップします。
4. 同様にBackgroundModeもドラックアンドドロップします。
5. Background ModeのRemote nortificationsにチェックを入れます。こちらでXcodeの設定は完了です。

#AppDelegate.swiftに通知の許可やDeviceの登録する処理を実装
最後にAppDelegateに処理を書いて終わりになります。
実装箇所は4箇所です。
1. import
2. didFinishLaunchingWithOptions
3. AppDelegateのクラス内
4. UNUserNotificationCenterDelegateのExtension

```swift
import UIKit
import Firebase
import FirebaseMessaging
import UserNotifications

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

   func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
       // Override point for customization after application launch.
       FirebaseApp.configure()
       if #available(iOS 10.0, *) {
           // For iOS 10 display notification (sent via APNS)
           UNUserNotificationCenter.current().delegate = self

           let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
           UNUserNotificationCenter.current().requestAuthorization(
               options: authOptions,
               completionHandler: {_, _ in })
       } else {
           let settings: UIUserNotificationSettings =
               UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
           application.registerUserNotificationSettings(settings)
       }

       application.registerForRemoteNotifications()
       
       return true
   }

   // MARK: UISceneSession Lifecycle

   func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
       // Called when a new scene session is being created.
       // Use this method to select a configuration to create the new scene with.
       return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
   }

   func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set<UISceneSession>) {
       // Called when the user discards a scene session.
       // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
       // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
   }
   
   func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
       // Print message ID.
       if let messageID = userInfo["gcm.message_id"] {
           print("Message ID: \(messageID)")
       }

       // Print full message.
       print(userInfo)
   }

   func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
                    fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
       // Print message ID.
       if let messageID = userInfo["gcm.message_id"] {
           print("Message ID: \(messageID)")
       }

       // Print full message.
       print(userInfo)

       completionHandler(UIBackgroundFetchResult.newData)
   }


}

@available(iOS 10, *)
extension AppDelegate : UNUserNotificationCenterDelegate {
   func userNotificationCenter(_ center: UNUserNotificationCenter,
                               willPresent notification: UNNotification,
                               withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
       let userInfo = notification.request.content.userInfo

       if let messageID = userInfo["gcm.message_id"] {
           print("Message ID: \(messageID)")
       }

       print(userInfo)

       completionHandler([])
   }

   func userNotificationCenter(_ center: UNUserNotificationCenter,
                               didReceive response: UNNotificationResponse,
                               withCompletionHandler completionHandler: @escaping () -> Void) {
       let userInfo = response.notification.request.content.userInfo
       if let messageID = userInfo["gcm.message_id"] {
           print("Message ID: \(messageID)")
       }

       print(userInfo)

       completionHandler()
   }
}
```
コピペでできると思いますので、そのように実装してください。
こちらでコードの設定も完了です。
アプリを実機に接続して、アプリを起動し、通知の許可のダイアログが出ると思うので許可した状態で置いておいてください。(通知の許可がないと通知が届きません。＋リモートの通知は実機でないと届かないです。)

#Cloud Messagingより通知を送信する
1. あとは通知を送信するだけです！Firebase Console左メニューよりCloud Messagingを選択します。
2. Send your firdt messageをクリックし、通知する内容を入力します。(適当で大丈夫です。)　次へをクリックします。
3. アプリを選択から自分のアプリを選択して次へをクリックします。
4. スケジュールは細かく設定できますが、今は現在で大丈夫です。次へをクリックします。
5. コンバージョンイベントは特に何も設定せず次へをクリックします。
6. 最後は通知音のON、OFFやIOSバッチ(アプリ右上につく赤い丸いやつ)の数量など細かく設定できます。設定はお任せします。問題なければ確認をクリックします。
7. 最後に公開をクリックすると通知はが発送され、実機に通知が届きます。
8. 以上で設定と通知が完了しました。

#プッシュ通知が正しく配信されない場合
1. 書類の作成順序を間違えている
2. 途中で作成した書類を編集した
3. 開発用証明書が複数作成されている
4. CSRファイルが複数作成されている
5. AppIDを作成時に「Push Notifications」にチェックを入れ忘れている
6. 実機でアプリが開きっぱになっていないか確認(アプリが開いてると届かないです。)
7. 通知の許可がうまくできていない可能性あるので、アプリを一度削除して再インストールする
8. Info.plistに変な設定をしていないか確認する。(記事によってはInfo.plistに設定が必要と書いてありますが特に必要ないです。)

#Firebase Admin SDKを使ったPush通知
Firebase Cloud Messaging (FCM)を利用して、サーバからスマホへのPush通知を試してみました。サーバからFirebase経由でPush通知を送信する方法を書いていきます。

#Firebase Admin SDKをインストール
```
$ pip install firebase-admin
```
サービス アカウントを認証して Firebase サービスへのアクセスを承認するには、秘密鍵ファイルを JSON 形式で生成する必要があります。
#Firebase consoleからfirebase接続用の鍵ファイルを取得
1. [Firebase コンソール](https://console.firebase.google.com/?hl=ja "Firebase コンソール")の左上にある歯車マークを押して、プロジェクトの設定画面を開きます。
2. この画面から「サービスアカウント」のタブをクリックします。
3. 「新しい秘密鍵の生成」ボタンを押すと、秘密鍵のファイルがダウンロードされます。
4. キーを含む JSON ファイルを安全に保管します。

#特定のスマホにPush通知
特定のスマホ(YOUR_REGISTRATION_TOKEN)へPush通知を送信します。
コードは以下です。path/to/serviceAccountKey.jsonは、ダウンロードした秘密鍵のファイルを指定します。

```swift
import firebase_admin
from firebase_admin import credentials
from firebase_admin import messaging

cred = credentials.Certificate("path/to/serviceAccountKey.json")
firebase_admin.initialize_app(cred)

# This registration token comes from the client FCM SDKs.
registration_token = 'YOUR_REGISTRATION_TOKEN'

# See documentation on defining a message payload.
message = messaging.Message(
    notification=messaging.Notification(
        title='test server',
        body='test server message',
    ),
    token=registration_token,
)

# Send a message to the device corresponding to the provided
# registration token.
response = messaging.send(message)
# Response is a message ID string.
print('Successfully sent message:', response)
```

#トピックに参加しているスマホにPush通知
特定のトピックに参加している複数のスマホにPush通知する方法です。
例えば、"weather"のトピックに参加しているスマホにPush通知するときは、メッセージの作成部分を以下のように変更します。
具体的には、tokenの部分をtopicに変更するだけです。

```swift
message = messaging.Message(
    notification=messaging.Notification(
        title='test server',
        body='test server message',
    ),
    topic='weather',
)
```
iosでトピックへの参加は、AppDelegate.swiftに以下のようなコードで実現できます。

```swift
Messaging.messaging().subscribe(toTopic: "weather") { error in
    print("Subscribed to weather topic")
}
```

#応用編 アイコン右上の数字（バッジ表示）をつける
今までは、タイトルとメッセージのみでしたが、バッジ処理（アイコン右上の数字）もつけてみようと思います。
以下のようなコードで実現できます。

```swift
import firebase_admin
from firebase_admin import credentials
from firebase_admin import messaging

cred = credentials.Certificate("path/to/serviceAccountKey.json")
firebase_admin.initialize_app(cred)

notification = messaging.Notification(
    title='test server',
    body='test server message',
)
topic='weather'

apns = messaging.APNSConfig(
   payload = messaging.APNSPayload(
       aps = messaging.Aps(badge = 1) #　ここがバックグランド通知に必要な部分
   )
)

message = messaging.Message(
    notification=notification,
    apns=apns,
    topic=topic,
)

response = messaging.send(message)
print('Successfully sent message:', response)
```
#終わりに
振り返ってみましたが、かなりの文字数になってしまいました。
それほど、大変ってことですかね？
長文を読んでくれて、ありがとうございました。
以下が参考にさせて頂いたリンクになります。
1. [Firebase を iOS プロジェクトに追加する](https://firebase.google.com/docs/ios/setup?hl=ja "Firebase を iOS プロジェクトに追加する")
2. [【Swift5】リモートプッシュ通知の実装方法](https://note.com/t0_inoue/n/nb817e740654b "【Swift5】リモートプッシュ通知の実装方法")
3. [プッシュ通知に必要な証明書の作り方2020](https://qiita.com/natsumo/items/d5cc1d0be427ca3af1cb "プッシュ通知に必要な証明書の作り方2020")
4. [Firebase Admin SDKを使ったPush通知](https://qiita.com/KenNagami/items/f9316b040aed0a5abc22 "Firebase Admin SDKを使ったPush通知")