iOS ≥ 10 Notification Service Extension の応用 (Swift)
画像プッシュ通知、プッシュ通知表示統計、プッシュ通知表示前処理
基本的なプッシュ通知の構築や仕組みについてはネット上に多くの情報があるため、ここでは詳しく説明しません。本記事の主なポイントは、アプリで画像プッシュ通知をサポートし、新機能を活用してより正確な通知表示の統計を実現する方法です。

上図のように、Notification Service Extensionはアプリがプッシュ通知を受け取った後に通知の事前処理を行い、その後通知内容を表示できます。
公式ドキュメントによると、プッシュ通知の内容を処理する際、処理時間の制限は約30秒です。もし30秒を超えてコールバックが呼ばれない場合、プッシュ通知はそのまま実行され、ユーザーの端末に表示されます。
サポート状況
iOS ≥ 10.0
30秒で何ができる?
- (目標1) プッシュ通知の画像URLから画像をダウンロードし、通知内容に添付する🏆

-
(目標2) プッシュ通知の表示有無を統計する🏆
-
プッシュ通知の内容変更および再構成
-
プッシュ通知内容の暗号化・復号(復号)表示
-
プッシュ通知を表示するかどうか決められる? =>> 答え:できません
まず、バックエンドのプッシュ通知プログラムのPayload部分
バックエンドでプッシュ通知を送る際に、構造体に "mutable-content":1 を追加する必要があります。これにより、システムはNotification Service Extensionを実行します。
{
"aps": {
"alert": {
"title": "新しい記事がおすすめです",
"body": "今すぐ確認"
},
"mutable-content":1,
"sound": "default",
"badge": 0
}
}
And… 最初のステップとして、プロジェクトに新しいターゲットを作成します

Step 1. Xcode -> ファイル -> 新規作成 -> ターゲット

Step 2. iOS -> Notification Service Extension -> 次へ

Step 3. プロダクト名を入力 -> 完了

Step 4. 「Activate」をクリックしてください。
ステップ2:プッシュ通知コンテンツ処理プログラムの作成

Product Name/NotificationService.swiftファイルを見つける
import UserNotifications
class NotificationService: UNNotificationServiceExtension {
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 bestAttemptContent = bestAttemptContent {
// 通知内容をここで変更します...
// プッシュ通知の内容をここで処理し、画像を読み込みます
bestAttemptContent.title = "\(bestAttemptContent.title) [modified]"
contentHandler(bestAttemptContent)
}
}
override func serviceExtensionTimeWillExpire() {
// システムによって拡張機能が終了される直前に呼ばれます。
// ここで「最善の試み」として修正した内容を配信する機会を利用してください。そうしないと元のプッシュペイロードが使用されます。
// タイムアウトになるので、画像は無視してタイトルだけ変更します
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
contentHandler(bestAttemptContent)
}
}
}
上記のコードでは、NotificationServiceには2つのインターフェースがあります。1つ目は didReceive で、通知が届いたときにこの関数が呼ばれます。処理が完了したら、contentHandler(bestAttemptContent) というコールバックメソッドを呼び出してシステムに通知します。
処理時間が長すぎてコールバックメソッドが呼ばれない場合、2番目の関数 serviceExtensionTimeWillExpire() がトリガーされます。タイムアウトとなり、基本的に復旧は不可能で、タイトルや内容の簡単な変更などの後片付けのみ行えます(例えば、ネットワークデータの読み込みは行いません)。
実践例
ここでは、Payloadが以下のようになっていると仮定します。
{
"aps": {
"alert": {
"push_id":"2018001",
"title": "新しい記事をおすすめします",
"body": "今すぐ確認",
"image": "https://d2uju15hmm6f78.cloudfront.net/image/2016/12/04/3113/2018/09/28/trim_153813426461775700_450x300.jpg"
},
"mutable-content":1,
"sound": "default",
"badge": 0
}
}
「push_id」と「image」はどちらもカスタムフィールドで、push_idはプッシュ通知を識別してサーバーに統計情報を送るために使います。imageはプッシュ通知に添付する画像のURLです。
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
if let bestAttemptContent = bestAttemptContent {
guard let info = request.content.userInfo["aps"] as? NSDictionary,let alert = info["alert"] as? Dictionary<String,String> else {
contentHandler(bestAttemptContent)
return
// 通知内容の形式が予期しないため、処理しない
}
// 目標2:
// サーバーに通知が表示されたことを返す
if let push_id = alert["push_id"],let url = URL(string: "顯示統計API網址") {
var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: 30)
request.httpMethod = "POST"
request.addValue(UserAgent, forHTTPHeaderField: "User-Agent")
var httpBody = "push_id=\(push_id)"
request.addValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.httpBody = httpBody.data(using: .utf8)
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
}
DispatchQueue.global().async {
task.resume()
// 非同期処理、結果は気にしない
}
}
// 目標1:
guard let imageURLString = alert["image"],let imageURL = URL(string: imageURLString) else {
contentHandler(bestAttemptContent)
return
// 画像が添付されていなければ特別な処理は不要
}
let dataTask = URLSession.shared.dataTask(with: imageURL) { (data, response, error) in
guard let fileURL = NSURL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(imageURL.lastPathComponent) else {
contentHandler(bestAttemptContent)
return
}
guard (try? data?.write(to: fileURL)) != nil else {
contentHandler(bestAttemptContent)
return
}
guard let attachment = try? UNNotificationAttachment(identifier: "image", url: fileURL, options: nil) else {
contentHandler(bestAttemptContent)
return
}
// 画像URLを読み込み、ダウンロードして端末に保存し、UNNotificationAttachmentを作成
bestAttemptContent.categoryIdentifier = "image"
bestAttemptContent.attachments = [attachment]
// 通知に画像の添付ファイルを追加
bestAttemptContent.body = (bestAttemptContent.body == "") ? ("すぐに確認") : (bestAttemptContent.body)
// bodyが空ならデフォルトの"すぐに確認"を設定
contentHandler(bestAttemptContent)
}
dataTask.resume()
}
}
serviceExtensionTimeWillExpire の部分は特に処理していないので、省略します。重要なのは上記の didReceive のコードです。
プッシュ通知を受信した際、まずAPIを呼び出してバックエンドに通知を受け取り、プッシュ通知を表示することを伝えます。これにより、バックエンドでプッシュ通知の統計を取りやすくなります。その後、画像が添付されている場合は画像の処理を行います。
In-App状態の時:
同様にNotification Service ExtensionのdidReceiveがトリガーされ、その後AppDelegateの func application( _ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any ], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) メソッドが呼ばれます。
付記:画像プッシュ通知についてさらに…
Notification Content Extensionを使って、プッシュ通知をタップしたときに表示するUIView(自分でカスタム可能)やタップ動作をカスタマイズする方法
参考はこちら: iOS10プッシュ通知の応用(Notification Extension)
iOS 12以降はより多くのアクション処理をサポート: iOS 12 新通知機能:通知にインタラクティブな複雑な機能を実装する
Notification Content Extensionの部分では、画像付きプッシュ通知を表示できるUIViewを一つ配置しただけで、あまり細かい調整はしていません:

Post は Medium から ZMediumToMarkdown によって変換されました。



コメント