iOS Push Notification with Notification Service Extension

Author - Bhavin & Jayesh

When push notification arrives in an iOS app,  you may want to be able to download content in response to it or edit the content before it’s shown to the user. In iOS 10 and later, Apple allows apps to do that using new Notification Service Extension.

The Following guide will help you to set up a project to display image in notification banner. The following steps describe how to enable group extension and notification service in the iOS application.

Setting up Xcode project

Following screenshot describe how to create the iOS application. First of all you need to create a single view application.

Create Notification Service Extension

After you have successfully created the iOS application you need to create a notification service extension. Here are the steps of how to create a notification service extension.

1. In the Xcode menu, go to File > New > Target.

2. Select the Notification Service Extension.

3. Gives the name of the Extension service and click the finish button.

After you create iOS application and Notification service extension, you will see the two target in two new files for notification service. Here are two new files.

1) NotificationService.swift:- In this file, you need to describe all the content and logic of the notification.

2) Info.plist:- Main property file and main settings of the extension. Here is a screenshot.

Enable Push Notification and App Group capabilities in iOS App Target

Once you have created iOS App and Notification Service Extension, you need to enable push notification and App Group in iOS application capabilities. Here are the screenshots, it describes how to enable both capabilities.

Enable App Group capabilities in Extension Target

After you enable both capabilities in iOS App Target, you need to enable App Group Extension in extension target. Here is a screenshot.

After you enable App Group capability in Extension target you get the ServiceExtension.entitlements file. Here are screenshots of the files. (Note: if app group not displaying the capabilities perfectly then you can add manually from file)

Here is a link of how to create certificate in apple developer account.

Get the permission of Push notification in application.

After complete the setup, we need to get permission for the push notification service in the application. We are adding permission code of push notification in AppDelegate.swift file. below is a code of how to get push notification permission in the application.

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
    // Override point for customization after application launch.
    UNUserNotificationCenter.current().delegate = self
       
    if #available(iOS 10, *) {
        UNUserNotificationCenter.current().requestAuthorization(options:[.badge, .alert, .sound]){ granted, error in }
    } else {
        application.registerUserNotificationSettings(UIUserNotificationSettings(types: [.badge, .sound, .alert], categories: nil))
    }
    application.registerForRemoteNotifications()
    return true
}
After that, you need to implement delegate methods of the UNUserNotificationCenter to get Device Token.
func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data){
    let deviceTokenString = deviceToken.reduce("") { $0 + String(format: "%02X", $1) }
    print("APNs device token: \(deviceTokenString)")
}
func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
    completionHandler()
}

Application Extension Lifecycle

Once you have configured your Application with a Notification Service Extension, the following process will take place for each notification:

  1. App receives notification.
  2. System creates an instance of your extension class and launches it in the background.
  3. Your extension performs content edits and/or downloads some content.
  4. If your extension takes too long to perform its work, it will be notified and immediately terminated.
  5. Notification is displayed to the user.

As you can see, when using a Notification Service Extension, you only have a limited amount of time to perform the necessary work. If your extension is taking too long, then it will be stopped by the system, and the notification will be delivered as if you had no extension at all

Extension Limitation

The last important thing to consider when using a notification service extension is the conditions under which the extension will operate.

Firstly, your extension will only be launched for notifications which are configured to show on-screen alerts to the user. This means that any silent notifications (like the ones used to update app badges) will not trigger your extension.

Secondly, the incoming notification’s APS dictionary within its payload must include the mutable-content key with a value of 1(TRUE).

The correct configuration of your notifications will ultimately depend on your app’s own setup. To meet the second condition, in particular, some changes may need to be made to your server-side push notification implementation. If you are not using a custom server and instead using a third-party service for your push notification implementation, then I would suggest researching and reading through their support documentation if you can’t get your extension to work.

Now configure NotificationService.swift file

This file is used to handle notification content before notification displayed to user.

Once notification arrived extension download image from URL and display in notification banner. I am storing data in shared user default to access this image into the main app without opening app using notification.

When you open app normally last downloaded file will always display in the main app. Below is a code of NotificationService.swift file.

First you need to import following frameworks

import UserNotifications

import UIKit

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)
        let defaults = UserDefaults(suiteName: "group.logistic.test")
        defaults?.set(nil, forKey: "images")
        defaults?.synchronize()
        
        guard let content = (request.content.mutableCopy() as? UNMutableNotificationContent) else {
            contentHandler(request.content)
            return
        }
        
        guard let apnsData = content.userInfo["data"] as? [String: Any] else {
            contentHandler(request.content)
            return
        }
        
        guard let attachmentURL = apnsData["attachment-url"] as? String else {
            contentHandler(request.content)
            return
        }
        
        do {
            let imageData = try Data(contentsOf: URL(string: attachmentURL)!)
            guard let attachment = UNNotificationAttachment.create(imageFileIdentifier: "image.jpg", data: imageData, options: nil) else {
                contentHandler(request.content)
                return
            }
            content.attachments = [attachment]
            contentHandler(content.copy() as! UNNotificationContent)
            
        } catch {
            contentHandler(request.content)
            print("Unable to load data: \(error)")
        }
    }
    
    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)
        }
    }
}

The following UNNotificationAttachment extension function to save the media to disk.

extension UNNotificationAttachment {
    static func create(imageFileIdentifier: String, data: Data, options: [NSObject : AnyObject]?)
        -> UNNotificationAttachment? {
            let fileManager = FileManager.default
            if let directory = fileManager.containerURL(forSecurityApplicationGroupIdentifier: "group.logistic.test") {
                do {
                    let newDirectory = directory.appendingPathComponent("Images")
                    if (!fileManager.fileExists(atPath: newDirectory.path)) {
                        try? fileManager.createDirectory(at: newDirectory, withIntermediateDirectories: true, attributes: nil)
                    }
                    let fileURL = newDirectory.appendingPathComponent(imageFileIdentifier)
                    do {
                        try data.write(to: fileURL, options: [])
                    } catch {
                        print("Unable to load data: \(error)")
                    }
                    
                    let defaults = UserDefaults(suiteName: "group.logistic.test")
                    defaults?.set(data, forKey: "images")
                    defaults?.synchronize()
                    let imageAttachment = try UNNotificationAttachment.init(identifier: imageFileIdentifier, url: fileURL,options: options)
                    return imageAttachment
                } catch let error {
                    print("error \(error)")
                }
            }
            return nil
    }
}

How to get image in application ViewController

Here is example code of getting image in ViewController.swift file. We have implemented the code in

viewDidAppear method.

override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        let defaults = UserDefaults(suiteName: "group.logistic.test")
        if (defaults?.object(forKey: "images") != nil) {
            let data = defaults?.value(forKey: "images") as! Data
            imgNotification.image = UIImage(data: data)
        }
    }

Notification Payload Sample

A very important step for sending media attachment is the mutable-content key in the push payload from the server. So let’s take a look at this example payload:

{   "aps": {
       "alert": "Hello!",
       "sound": "default",
       "mutable-content": 1,
       "badge": 1,
   },
   "data": {
       "attachment-url": "https://abc.com/sample_image.jpg"
   }
}

Bellow is sample Xcode project for your reference.

Don’t miss the next post!

Loading

Related Posts