Home iOS ≥ 10 Notification Service Extension 應用 (Swift)
Post
Cancel

iOS ≥ 10 Notification Service Extension 應用 (Swift)

iOS ≥ 10 Notification Service Extension 應用 (Swift)

圖片推播、推播顯示統計、推播顯示前處理

關於基礎的推播建置、推播原理;網路資料很多,這邊就不再論述,本篇主要重點在如何讓APP支援圖片推播及運用新特性達成更精準的推播顯示統計.

如上圖所示,Notification Service Extension讓你在APP收到推播後能針對推播做預處理,然後才顯示推播內容

官方文件寫到,我們針對推播進來的內容做處理時,處理時限大約30秒鐘,如果超過30秒還沒CallBack,推播就會繼續執行,出現在使用者的手機.

支援度

iOS ≥ 10.0

30秒可以幹嘛?

  • (目標1) 從推播內容的圖片連結欄位下載圖片回來,並附加到推播內容上🏆

  • (目標2) 統計推播有無顯示🏆
  • 推播內容修改、重組內容
  • 推播內容加解密(解密)顯示
  • 決定推播要不要顯示?答案:不行

首先,後端推播程式的 Payload 部分

後端在推播時的結構要多加上一行 “mutable-content":1 系統收到推播才會執行Notification Service Extension

1
2
3
4
5
6
7
8
9
10
11
{
    "aps": {
        "alert": {
            "title": "新文章推薦給您",
            "body": "立即查看"
        },
        "mutable-content":1,
        "sound": "default",
        "badge": 0
    }
}

And… 第一步,為專案新建一個Target

Step 1. Xcode -> File -> New -> Target

Step 1. Xcode -> File -> New -> Target

Step 2. iOS -> Notification Service Extension -> Next

Step 2. iOS -> Notification Service Extension -> Next

Step 3. 輸入Product Name -> Finish

Step 3. 輸入Product Name -> Finish

Step 4. 點選 Activate

Step 4. 點選 Activate

第二步,撰寫推播內容處理程式

找到Product Name/NotificationService.swift檔

找到Product Name/NotificationService.swift檔

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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 {
            // Modify the notification content here...
            // 推播內容在這處理,Load 圖片回來
            bestAttemptContent.title = "\(bestAttemptContent.title) [modified]"
            
            contentHandler(bestAttemptContent)
        }
    }
    
    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)
        }
    }

}

如上程式碼,NotificationService有兩個接口;第一個是 didReceive 當有推播進來時會觸發這個function,其中當處理完畢後需要呼叫 contentHandler(bestAttemptContent) 這個CallBack Method告知系統

如果時間過久都沒呼叫CallBack Method,就會觸發第二個 function serviceExtensionTimeWillExpire() 已逾時,基本上已回天乏術,只能做一些收尾的動作(例如:單純改改標題、內容,不Load網路資料了)

實戰範例

這裡假設我們的 Payload 如下

1
2
3
4
5
6
7
8
9
10
11
12
13
{
    "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 則是推播要附加的圖片內容之圖片網址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
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:
        //回傳Server,告知推播有顯示
        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
            }
            //以上為讀取圖片連結並下載到手機並放入建立UNNotificationAttachment
            
            bestAttemptContent.categoryIdentifier = "image"
            bestAttemptContent.attachments = [attachment]
            //為推播添加附件圖片
            
            bestAttemptContent.body = (bestAttemptContent.body == "") ? ("立即查看") : (bestAttemptContent.body)
            //如果body為空,則用預設內容"立即查看"
            
            contentHandler(bestAttemptContent)
        }
        dataTask.resume()
    }
}

serviceExtensionTimeWillExpire 的部分我沒特別處理什麼,就不貼了;關鍵還是上述 didReceive 的程式碼

可以看到當接受到有推播通知時,我們先Call 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 並沒有做太多琢磨:

結婚吧APP

結婚吧APP

===

本文首次發表於 Medium ➡️ 前往查看

This post is licensed under CC BY 4.0 by the author.

iOS UITextView 文繞圖編輯器 (Swift)

Vision 初探 — APP 頭像上傳 自動識別人臉裁圖 (Swift)

Comments powered by Disqus.