ylliX - Online Advertising Network

Simple UNNotificationServiceExtension with Firebase Push and image display

As you probably already know, sending push notification from Firebase will not display attached image on device. This example assumes you know how to configure push notifications in your app with Firebase. So let’s start.

But, why bother and create your own extension? Because starting from iOS 10 sending such message from Firebase console:

will cause your device to display notification this way:

and as you can see there is no image at all.

So lets start and create new target selecting “Notification Service Extension”:

Give it some name, and you should see new files group on left, and new target:

Now your extension should look like this:

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...
            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)
        }
    }

}

We need to modify didReceive method which is called when push message arrives. At this stage, you can try to send a message to your device, and you should see it like this:

and this means our extension was called by system, and we modified title by adding “[modified]” string.

Firebase attaches image in fcm_options dictionary so we need to pull it out, download and save to some temporary directory. So lets add UNNotificationAttachment init extension which will take our Data and save it in temporary folder:

extension UNNotificationAttachment {

    convenience init(data: Data, options: [NSObject: AnyObject]?) throws {
        let fileManager = FileManager.default
        let temporaryFolderName = ProcessInfo.processInfo.globallyUniqueString
        let temporaryFolderURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(temporaryFolderName, isDirectory: true)

        try fileManager.createDirectory(at: temporaryFolderURL, withIntermediateDirectories: true, attributes: nil)
        let imageFileIdentifier = UUID().uuidString + ".jpg"
        let fileURL = temporaryFolderURL.appendingPathComponent(imageFileIdentifier)
        try data.write(to: fileURL)
        try self.init(identifier: imageFileIdentifier, url: fileURL, options: options)
    }
}

In next step we will add new method to NotificationService which will handle image downloading, and creating UNNotificationAttachment in result:

func parseAttachment(imageUrl: String) -> UNNotificationAttachment? {
    if let imageData = try? Data(contentsOf: URL(string: imageUrl)!) {
        return try? UNNotificationAttachment(data: imageData, options: nil)
    }
    
    return nil
}

And final step is changing didReceive method to make use of our new method, the final version should look like this:

override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
    self.contentHandler = contentHandler
    bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
    defer {
        contentHandler(bestAttemptContent ?? request.content)
    }
    let fcm_options = request.content.userInfo["fcm_options"] as? [String : Any]
    let imageUrl = fcm_options?["image"] as? String
    if let imgUrl = imageUrl {
        if let attachment = parseAttachment(imageUrl: imgUrl) {
            bestAttemptContent?.attachments = [attachment]
            return
        }
    }
}

And what happens here? First we are pulling fcm_options into [String : Any] dictionary, and then we need to extract image url from “image” key in dictionary. If we found image url, we are calling parseAttachment method which downloads image and returns attachment which is added to UNNotificationContent required in callback. So after sending another test push from Firebase you should see it with image:

And after expading you should see full image:

You can grab sample project from GitHub.

One thought on “Simple UNNotificationServiceExtension with Firebase Push and image display

Leave a Reply