Firebase を使用してiOSのプッシュ通知を実装する

前回のプッシュ通知の記事より1から実装するとしんどいことがわかったので別のアプローチを考えてみる。
この記事みるよりhttps://firebase.google.com/docs/ を見たほうがいいです(身も蓋もない)


2016-12-12 : 通知取得時の通知内容(titlteやbodyなど)を取得する方法が見当たらなかったので追記


2016-12-22 : FirebaseへのAPNs証明書がハマりそうだったので追記


2017-01-18 : 通知が届かないケースがあったため追記









Firebase について



  • iOS,Android,Webの通知機能が実装できる
  • 単純なテキスト通知だけでなくカスタムデータの設定や投稿日時の指定ができる
  • 通知日時などのログが後から確認できる
  • 通知送信数や既読数が確認できる
  • 全体、あらかじめ設定したトピック単位、特定の端末へ通知を送信できる
  • 通知機能だけなら $0 / month
  • サーバサイドプログラムからPush通知が実行できるらしく大体の要望には答えられる模様




Firebase 利用前の事前準備



事前に証明書.p12 を書き出す必要あり
証明書.p12
前回の記事 http://trueman-developer.blogspot.com/2016/11/iosphpswift3.htmlを参照のこと

正直 https://firebase.google.com/docs/cloud-messaging/ios/certs の方が丁寧・正確なのでなんとも言えない

ざっくり説明すると
MacOS上でCSR作成
→ https://developer.apple.com/account/ios/identifier/bundle よりAppID作成(Push Notifications を有効にすること)
→ 作成したApp ID からEdit → Create Certificate... → CSRをアップロードして証明書ダウンロード
→ ダブルクリックしてキーチェーンアクセスに登録した後右クリックから書き出し(コマンドからでもいける?)
秘密鍵は不要なので注意
パスワードは入力不要



Firebase プロジェクト作成から組み込み、動作確認まで




https://console.firebase.google.com/ へアクセス
?を押せば丁寧な説明が表示される
正直ハマりポイントはないはず

iOS アプリ向けにに Firebaseプロジェクトを作成する


Firebaseへようこそ

プロジェクトの作成

プロジェクト名
適宜設定する

国 / 地域
Firebaseが動作する場所とは関係なく通貨単位などが設定されるらしい。

iOSアプリにFirebaseを追加

iOSバンドルID
iOSアプリのBundleIDと合わせること

アプリのニックネーム(省略可)
Firebase上での識別名

App Store ID(省略可)
アプリ公開後の話になってくるので基本省略することになるかと

初回のみ手順が表示される。
軽く手順を確認しながら進める(一旦閉じないと2番目以降の作業ができない)


GoogleService-Info.plist をプロジェクトへ追加する
ハンバーガーアイコンだっけ?(アプリ名の右の...)をクリック → 管理
→ 全般タブ → GoogleService-info.plist からダウンロード → プロジェクトに追加

Firebase Overview

GoogleService-info.plist ダウンロード

ハンバーガーアイコン?(アプリ名の右の...)をクリック → 管理
→ クラウドメッセージングタブ → 証明書をアップロード → 証明書.p12をアップロード
証明書.p12 はキーチェーンアクセスから種類が証明書のものを1つ書き出すこと(Firebaseのドキュメントではなぜか秘密鍵と記述されている)

APNs証明書アップロード

Firebase/Core と Firebase/Messaging をインストール

Cocoa Podsを使用する。
プロジェクトディレクトリで
pod init

作成されたPodfileを編集する。

Push通知を利用する場合は
pod ‘Firebase/Core’
pod 'Firebase/Messaging'
を追加すること

大体以下のようになる。
# Uncomment this line to define a global platform for your project
# platform :ios, '9.0'

target 'webViewTest' do
# Comment this line if you're not using Swift and don't want to use dynamic frameworks
use_frameworks!

# Pods for webViewTest
pod ‘Firebase/Core’
pod 'Firebase/Messaging'

target 'webViewTestTests' do
inherit! :search_paths
# Pods for testing
end

target 'webViewTestUITests' do
inherit! :search_paths
# Pods for testing
end

end


編集が完了したら一旦Xcodeを終了して
pod install

Cocoa Pods についての詳細は http://trueman-developer.blogspot.com/2016/10/cocoapodsios.html

Cocoa Pods をどうしても使いたくない人は
https://dl.google.com/firebase/sdk/ios/3_2_0/Firebase.zip
からダウンロードしてきてREADMEを参考にプロジェクトへ組み込む



iOS側の実装

プロジェクトを開いてAppDelegate.Swift へ以下を記述する
言語はSwift3

import UIKit
import UserNotifications
import Firebase


@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    
    var window: UIWindow?
    
    func application(_ application: UIApplication,
                     didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
        // Override point for customization after application launch.
        
        // 通知許可を求める 適切なタイミングで呼ぶこと
        let apnsTypes : UIUserNotificationType = [.badge, .sound, .alert]
        let notiSettings = UIUserNotificationSettings(types: apnsTypes, categories: nil)
        application.registerUserNotificationSettings(notiSettings)
        application.registerForRemoteNotifications()
 
        FIRApp.configure()
        NotificationCenter.default.addObserver(
            self,
            selector: #selector(self.tokenRefreshNotification),
            name: NSNotification.Name.firInstanceIDTokenRefresh,
            object: nil)
        
        if let refreshedToken = FIRInstanceID.instanceID().token() {
            print("InstanceID token: \(refreshedToken)")
            connectToFcm()
        }
        
        return true
    }
    
    func application(_ application: UIApplication,
                     didRegister notificationSettings: UIUserNotificationSettings) {
        if notificationSettings.types != .none {
            application.registerForRemoteNotifications()
        }
    }
    
    // デバイストークン取得時の処理
    func application(_ application: UIApplication,
                     didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
        
        let tokenText = deviceToken.map { String(format: "%.2hhx", $0) }.joined()
        print("deviceToken = \(tokenText)")
        FIRInstanceID.instanceID().setAPNSToken(deviceToken, type: FIRInstanceIDAPNSTokenType.sandbox)
        //FIRInstanceID.instanceID().setAPNSToken(deviceToken, type: FIRInstanceIDAPNSTokenType.unknown)
    }
    
    func tokenRefreshNotification(_ notification: Notification) {
        if let refreshedToken = FIRInstanceID.instanceID().token() {
            print("InstanceID token: \(refreshedToken)")
        }
        // Connect to FCM since connection may have failed when attempted before having a token.
        connectToFcm()
    }
    
    func tokenRefreshNotification(_ notification: Notification) {
        if let refreshedToken = FIRInstanceID.instanceID().token() {
            print("InstanceID token: \(refreshedToken)")
        }
        // Connect to FCM since connection may have failed when attempted before having a token.
        connectToFcm()
    }
    
    func connectToFcm() {
        FIRMessaging.messaging().connect { (error) in
            if error != nil {
                print("Unable to connect with FCM. \(error)")
            } else {
                print("Connected to FCM.")
            }
        }
    }
    
    // 通知取得時処理
    func application(_ application: UIApplication,
                     didReceiveRemoteNotification userInfo: [AnyHashable : Any],
                     fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
        print(userInfo)
        
        // 通知内容の詳細を取得
        let aps = userInfo["aps"] as? NSDictionary
        let alert = aps?["alert"] as? NSDictionary
        let title = userInfo["title"] as? String
        let body = userInfo["body"] as? String
    }





Firebaseの管理画面からpush通知を送る


Firebaseからプッシュ通知を送る

firebase 最初のメッセージを送信

https://console.firebase.google.com/project/[プロジェクト名]/notification を開く

→ 最初のメッセージを送信
メッセージ文:通知画面に表示される
メッセージラベル:ソースないで利用する
配信日:1ヶ月後まで指定可能
ターゲット:ユーザーセグメント
アプリ:送信対象のBundle ID を指定
→ メッセージを送信

Firebase プッシュ通知送信

プロジェクト名とメッセージ文が表示される。


アプリ起動中は通知に表示されない模様、起動中なんだからアプリ上でどうにかしろという設計思想?


HTTP通信を用いてFirebaseからpush通知を送る

https://firebase.google.com/docs/cloud-messaging/server
Firebase Cloud Messagingというらしい


URL https://fcm.googleapis.com/fcm/send に対して

ヘッダ
Authorization: key=[サーバキー]
Content-Type: application/json
を指定して
json形式で
{
"to": [モバイルインスタンスID],
"priority":"high",
"notification":
{
"title": "title", "body": "body"
}
}
をpostすると通知が飛ぶ。


サーバキーは管理画面から確認可能。
Firebase サーバーキー

モバイルインスタンスIDはiOSよりtokenRefreshNotificationで取得可能。
デバイストークンとは別物なので注意

以下のコマンドでプッシュ通知を送信可能
curl --header "Authorization: key=[サーバキー]" --header "Content-Type:application/json" https://fcm.googleapis.com/fcm/send -d "{\"to\": \"[モバイルインスタンスID]\",\"priority\":\"high\",\"notification\": {\"title\": \"title\", \"body\": \"body\"}}"

priorityをhighにしておかないとバックグラウンドへ行った時に通知が届かない。
現時点では画面から通知する場合と使用可能な機能が違うので注意(例えば全体に通知や言語ごとに通知などは出来ない)



以降もう少し詳しい話は https://firebase.google.com/docs/ を参照されたし




but you still need to add "remote-notification" to the list of your supported UIBackgroundModes in your Info.plist




iOS9以降?
TARGET → [Project] → Capabilities → Background Modes をON → Remote notifications にチェック

Background Modes → Remote notifications

これをやっておかないとアプリケーションを完全に終了させたとき通知が届かなくなる?

その他の情報
mBaasのFirebaseの機能と料金体系についてざっくり調べてみた

2016年11月9日水曜日