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

iOS ≥ 10 Notification Service Extension Application (Swift)

iOS ≥ 10 Notification Service Extension Application (Swift)

Image push notifications, push notification display statistics, pre-processing before push notification display

Regarding the basics of push notification setup and principles; there is a lot of information available online, so it will not be discussed here. This article focuses on how to enable the app to support image push notifications and use new features to achieve more accurate push notification display statistics.

As shown in the image above, the Notification Service Extension allows you to pre-process the push notification after the app receives it, and then display the push notification content.

The official documentation states that when we process the incoming push notification content, the processing time limit is about 30 seconds. If the callback is not made within 30 seconds, the push notification will continue to execute and appear on the user’s phone.

Support

iOS ≥ 10.0

What can be done in 30 seconds?

  • (Goal 1) Download the image from the image link field in the push notification content and attach it to the push notification content 🏆

  • (Goal 2) Statistics on whether the push notification was displayed 🏆
  • Modify and reorganize the push notification content
  • Encrypt and decrypt (decrypt) the push notification content for display
  • Decide whether to display the push notification? => Answer: No

First, the Payload part of the backend push notification program

The structure of the backend push notification needs to add a line "mutable-content":1 for the system to execute the Notification Service Extension when it receives the push notification

1
2
3
4
5
6
7
8
9
10
11
{
    "aps": {
        "alert": {
            "title": "New article recommended for you",
            "body": "Check it out now"
        },
        "mutable-content":1,
        "sound": "default",
        "badge": 0
    }
}

And… Step one, create a new Target for the project

**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.** Enter Product Name -> Finish

Step 3. Enter Product Name -> Finish

**Step 4.** Click Activate

Step 4. Click Activate

Step two, write the push notification content processing program

Find the Product Name/NotificationService.swift file

Find the Product Name/NotificationService.swift file

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
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...
            // Process the push notification content here, load the image back
            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.
        // Time is about to expire, ignore the image, just modify the title content
        if let contentHandler = contentHandler, let bestAttemptContent =  bestAttemptContent {
            contentHandler(bestAttemptContent)
        }
    }

}

As shown in the code above, NotificationService has two interfaces; the first one is didReceive, which is triggered when a push notification arrives. After processing, you need to call the contentHandler(bestAttemptContent) callback method to inform the system.

If the callback method is not called within a certain time, the second function serviceExtensionTimeWillExpire() will be triggered due to timeout. At this point, there’s not much you can do except some final touches (e.g., simply changing the title or content without loading network data).

Practical Example

Here we assume our payload is as follows:

1
2
3
4
5
6
7
8
9
10
11
12
13
{
    "aps": {
        "alert": {
            "push_id":"2018001",
            "title": "New Article Recommended for You",
            "body": "Check it out now",
            "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” and “image” are custom fields. The push_id is used to identify the push notification for easier tracking and reporting back to the server; the image is the URL of the image content to be attached to the push notification.

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
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
            // Push notification content format is not as expected, do not process
        }
        
        // Goal 2:
        // Report to the server that the push notification has been displayed
        if let push_id = alert["push_id"], let url = URL(string: "Display Statistics API URL") {
            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()
                // Asynchronous processing, ignore it
            }
        }
        
        // Goal 1:
        guard let imageURLString = alert["image"], let imageURL = URL(string: imageURLString) else {
            contentHandler(bestAttemptContent)
            return
            // If no image is attached, no special processing is needed
        }
        
        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
            }
            // The above reads the image link, downloads it to the phone, and creates a UNNotificationAttachment
            
            bestAttemptContent.categoryIdentifier = "image"
            bestAttemptContent.attachments = [attachment]
            // Add the image attachment to the push notification
            
            bestAttemptContent.body = (bestAttemptContent.body == "") ? ("Check it out now") : (bestAttemptContent.body)
            // If the body is empty, use the default content "Check it out now"
            
            contentHandler(bestAttemptContent)
        }
        dataTask.resume()
    }
}

serviceExtensionTimeWillExpire part I didn’t handle specifically, so I won’t paste it; the key is still the didReceive code mentioned above.

You can see that when a push notification is received, we first call the API to inform the backend that it has been received and will be displayed, which helps us with push notification statistics in the backend; then, if there is an attached image, we process the image.

In-App state:

The Notification Service Extension didReceive will still be triggered, followed by the AppDelegate’s func application( _ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable : Any ], fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) method.

Note: Regarding image push notifications, you can also…

Use Notification Content Extension to customize the UIView to be displayed when the push notification is pressed (you can create it yourself), as well as the actions upon pressing.

Refer to this article: iOS10 Advanced Push Notifications (Notification Extension)

iOS 12 and later supports more action handling: iOS 12 New Notification Features: Adding Interactivity and Implementing Complex Functions in Notifications

For the Notification Content Extension part, I only created a UIView to display image push notifications without much elaboration:

[Wedding App](https://itunes.apple.com/tw/app/%E7%B5%90%E5%A9%9A%E5%90%A7-%E4%B8%8D%E6%89%BE%E6%9C%80%E8%B2%B4-%E5%8F%AA%E6%89%BE%E6%9C%80%E5%B0%8D/id1356057329?ls=1&mt=8){:target="_blank"}

Wedding App

If you have any questions or feedback, feel free to contact me.

===

本文中文版本

===

This article was first published in Traditional Chinese on Medium ➡️ View Here


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

iOS UITextView Text Wrapping Editor (Swift)

Exploring Vision — APP Avatar Upload Automatic Face Detection and Cropping (Swift)