1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【2024/10時点】iOSアプリでFCMを使った画像付き通知を受信する方法

Last updated at Posted at 2024-10-11

何をするのか

iOSにて、FCM(Firebase Cloud Messaging)を使って以下のような画像付き通知を受信します。

落とし穴

いくつか落とし穴があります。気をつけてください。

  • Firebase(GUI)から通知を送ると、受信するまでに1〜2分ほどラグが発生します
    • PythonやGoなどでアプリサーバーをたて、Firebaseのプッシュ通知サーバー(https://fcm.googleapis.com/v1/projects/プロジェクト名//messages:send)へリクエストを送りプッシュ通知を送信してください。これでラグ発生することなく通知を受信できます。
  • 画像付き通知を受信するには、iOSアプリ側でNotification Service app extension を使用してリモート通知のペイロードを編集する必要があります。
    • 対処は2つ後のセクション(iOSアプリ側でNotification Service app extension を使用してリモート通知のペイロードを編集する)に書いておきます

アプリサーバー経由でFCM通知サーバーへアクセスする

今回は、Python(FastAPI)×Render でアプリサーバーを構築し、通知サーバーへアクセスしています。
以下、Pythonのコードです。プロジェクトに依存している箇所は変更していますので、適宜自身のプロジェクトと照らし合わせて変更してください mm

実装を見ると分かりますが、アプリサーバー経由で通知サーバーへアクセスする際、アクセストークンが必要だったり、リクエストパラメータを間違えないよう設定する必要があります。(パラメータに関するリファレンスはコードの下に示しておきます)

main.py
# API
from fastapi import FastAPI   # pip install fastapi
from pydantic import BaseModel  # リクエストbodyを定義するために必要
import uvicorn   # pip install uvicorn

# FCMを用いたPush通知
import requests   # pip install requests
from oauth2client.service_account import ServiceAccountCredentials   # pip install oauth2client
import json

# パラメータ
PROJECT_ID = ''   # 任意
JSON_PATH = '〜.json'  # 任意
BASE_URL = 'https://fcm.googleapis.com'
FCM_ENDPOINT = 'v1/projects/' + PROJECT_ID + '/messages:send'
FCM_URL = BASE_URL + '/' + FCM_ENDPOINT
print(FCM_URL)
SCOPES = ['https://www.googleapis.com/auth/firebase.messaging']

def _get_access_token():
  """Retrieve a valid access token that can be used to authorize requests.

  :return: Access token.
  """
  credentials = ServiceAccountCredentials.from_json_keyfile_name(
      JSON_PATH , SCOPES)
  access_token_info = credentials.get_access_token()
  return access_token_info.access_token

def _send_fcm_message(fcm_message):

    """Send HTTP request to FCM with given message.
    Args:
        fcm_message: JSON object that will make up the body of the request.
    """
    # [START use_access_token]
    headers = {
        'Authorization': 'Bearer ' + _get_access_token(),
        'Content-Type': 'application/json; UTF-8',
    }
    # [END use_access_token]
    resp = requests.post(FCM_URL, data=json.dumps(fcm_message), headers=headers)

    if resp.status_code == 200:
        print('Message sent to Firebase for delivery, response:')
        print(resp.text)
        return True
    else:
        print('Unable to send message to Firebase')
        print(resp.text)
        return False


# main
app = FastAPI()

# POSTでiOSアプリから受け取るデータ
class ToUser(BaseModel):
    user_fcm_token: str
    title: str
    body: str
    imageURL: str

# POST通信
@app.post("/pushNotification/")
def create_pushNotification(toUser: ToUser):
    common_message = {
        "message":{ 
            "token": toUser.user_fcm_token, 
            "notification":{ 
                "body": toUser.body, 
                "title": toUser.title,
                "image": toUser.imageURL
            },
            "apns":{
                "payload":{
                    "aps":{
                        "mutable-content":1
                    }
                },
                "fcm_options": {
                    "image": toUser.imageURL
                }
            }
        }
    }   # POSTでデータを送るためにJSON形式に変換
    print(json.dumps(common_message, indent=2))
    
    # アクセストークンの取得も行なっている
    isPushNotification = _send_fcm_message(common_message)

    if isPushNotification:
        return {"id": 0, "result": "success"}
    else:
        return {"id": 0, "result": "failure"}

FCM通知サーバーへのリクエストパラメータに関するリファレンス

iOSアプリ側でNotification Service app extension を使用してリモート通知のペイロードを編集する

画像付き通知の 画像 を受信しバナーへ表示するためには、Notification Service app extension を用いて画像ダウンロードするプログラムを実装する必要があります。

以下2つのサイトを見ながら(漏れなく)実装追加してみてください。

1. プロジェクトに Notification Service app extension を追加する

2. NotificationService.swift を編集し画像ダウンロードを実装

NotificationService.swift
// 1 にて自動生成されるファイル 手動で追加するものではないので注意

class NotificationService: UNNotificationServiceExtension {
    var contentHandler: ((UNNotificationContent) -> Void)?
    var bestAttemptContent: UNMutableNotificationContent?

    override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
        guard let content = (request.content.mutableCopy() as? UNMutableNotificationContent) else {
            contentHandler(request.content)
            return
        }

        let userInfo = request.content.userInfo as NSDictionary
        let fcmOptions = userInfo["fcm_options"] as? [String: String]

        guard let fcmOptions = fcmOptions,
            let url = URL(string: fcmOptions["image"] ?? "")
        else {
            contentHandler(content)
            return
        }

        print(url)

        let task = URLSession.shared.dataTask(
            with: url,
            completionHandler: { data, _, error in
                let fileName = url.lastPathComponent
                let writePath = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(fileName)

                do {
                    try data?.write(to: writePath)
                    let attachment = try UNNotificationAttachment(identifier: fileName, url: writePath, options: nil)
                    content.attachments = [attachment]
                    contentHandler(content)
                } catch {
                    print("error: \(error)")
                    contentHandler(content)
                }
            })
        task.resume()
    }

    override func serviceExtensionTimeWillExpire() {
        // Called just before the extension will be terminated by the system.
        // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
        if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
            contentHandler(bestAttemptContent)
        }
    }
}

アプリサーバーの構築Notification Service app extension を用いた実装 を済ませれば、本記事冒頭に示したように画像付き通知が受信可能なはずです。

それでは、良いFCMライフを!

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?