UNNotificationAttachment with UIImage or Remote URL

  1. create directory in tmp folder
  2. write the NSData representation of the UIImage into the newly created directory
  3. create the UNNotificationAttachment with url to the file in tmp folder
  4. clean up tmp folder

I wrote an extension on UINotificationAttachment

extension UNNotificationAttachment {

    static func create(identifier: String, image: UIImage, options: [NSObject : AnyObject]?) -> UNNotificationAttachment? {
        let fileManager = FileManager.default
        let tmpSubFolderName = ProcessInfo.processInfo.globallyUniqueString
        let tmpSubFolderURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(tmpSubFolderName, isDirectory: true)
        do {
            try fileManager.createDirectory(at: tmpSubFolderURL, withIntermediateDirectories: true, attributes: nil)
            let imageFileIdentifier = identifier+".png"
            let fileURL = tmpSubFolderURL.appendingPathComponent(imageFileIdentifier)
            let imageData = UIImage.pngData(image)
            try imageData()?.write(to: fileURL)
            let imageAttachment = try UNNotificationAttachment.init(identifier: imageFileIdentifier, url: fileURL, options: options)
            return imageAttachment
        } catch {
            print("error " + error.localizedDescription)
        }
        return nil
    }
}

So to create UNUserNotificationRequest with UNUserNotificationAttachment from a UIImage you can simply do sth like this

let identifier = ProcessInfo.processInfo.globallyUniqueString
let content = UNMutableNotificationContent()
content.title = "Hello"
content.body = "World"
if let attachment = UNNotificationAttachment.create(identifier: identifier, image: myImage, options: nil) {
    // where myImage is any UIImage
    content.attachments = [attachment] 
}
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 120.0, repeats: false)
let request = UNNotificationRequest.init(identifier: identifier, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request) { (error) in
    // handle error
}

This should work since UNNotificationAttachment will copy the image file to an own location.


I've created a blogpost on this subject, focused on GIF images. But it should be easy to rewrite my code for simply images.

You need to create a Notification Service Extension: Notification Service Extension

And include this code:

final class NotificationService: UNNotificationServiceExtension {

    private var contentHandler: ((UNNotificationContent) -> Void)?
    private var bestAttemptContent: UNMutableNotificationContent?

    override internal func didReceiveNotificationRequest(request: UNNotificationRequest, withContentHandler contentHandler: (UNNotificationContent) -> Void){
        self.contentHandler = contentHandler
        bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)

        func failEarly() {
            contentHandler(request.content)
        }

        guard let content = (request.content.mutableCopy() as? UNMutableNotificationContent) else {
            return failEarly()
        }

        guard let attachmentURL = content.userInfo["attachment-url"] as? String else {
            return failEarly()
        }

        guard let imageData = NSData(contentsOfURL:NSURL(string: attachmentURL)!) else { return failEarly() }
        guard let attachment = UNNotificationAttachment.create("image.gif", data: imageData, options: nil) else { return failEarly() }

        content.attachments = [attachment]
        contentHandler(content.copy() as! UNNotificationContent)
    }

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

}

extension UNNotificationAttachment {

    /// Save the image to disk
    static func create(imageFileIdentifier: String, data: NSData, options: [NSObject : AnyObject]?) -> UNNotificationAttachment? {
        let fileManager = NSFileManager.defaultManager()
        let tmpSubFolderName = NSProcessInfo.processInfo().globallyUniqueString
        let tmpSubFolderURL = NSURL(fileURLWithPath: NSTemporaryDirectory()).URLByAppendingPathComponent(tmpSubFolderName, isDirectory: true)

        do {
            try fileManager.createDirectoryAtURL(tmpSubFolderURL!, withIntermediateDirectories: true, attributes: nil)
            let fileURL = tmpSubFolderURL?.URLByAppendingPathComponent(imageFileIdentifier)
            try data.writeToURL(fileURL!, options: [])
            let imageAttachment = try UNNotificationAttachment.init(identifier: imageFileIdentifier, URL: fileURL!, options: options)
            return imageAttachment
        } catch let error {
            print("error \(error)")
        }

        return nil
    }
}

For more information you can check my blogpost here: http://www.avanderlee.com/ios-10/rich-notifications-ios-10/