I had the issue until I figured out I was not sending the url in data
parameter from FCM.
I made it work using these steps from this answer:
Step 1 - Add a notification service extension
Step 2 - Add Target to Podfile
Step 3 - Use Extension Helper
//
// NotificationService.swift
// RichNotification
//
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)
guard let bestAttemptContent = bestAttemptContent,
let attachmentURLAsString = bestAttemptContent.userInfo["image"] as? String,
let attachmentURL = URL(string: attachmentURLAsString) else {
return
}
downloadImageFrom(url: attachmentURL) { (attachment) in
if let attachment = attachment {
bestAttemptContent.attachments = [attachment]
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)
}
}
private func downloadImageFrom(url: URL, with completionHandler: @escaping (UNNotificationAttachment?) -> Void) {
let task = URLSession.shared.downloadTask(with: url) { (downloadedUrl, response, error) in
// 1. Test URL and escape if URL not OK
guard let downloadedUrl = downloadedUrl else {
completionHandler (nil)
return
}
// 2. Get current's user temporary directory path
var urlPath = URL(fileURLWithPath: NSTemporaryDirectory ())
// 3. Add proper ending to url path, in the case jpg (The system validates the content of attached files before scheduling the corresponding notification request. If an attached file is corrupted,
let uniqueURLEnding = ProcessInfo.processInfo.globallyUniqueString + ".jpg"
urlPath = urlPath.appendingPathComponent (uniqueURLEnding)
// 4. Move downloadedUrl to newly created urlPath
try? FileManager.default.moveItem(at: downloadedUrl, to: urlPath)
// 5. Try adding getting the attachment and pass it to the completion handler
do {
let attachment = try UNNotificationAttachment (identifier: "picture", url: urlPath, options: nil)
completionHandler(attachment)
}
catch {
completionHandler(nil)
}
}
task.resume()
}
}
extension UNNotificationRequest {
var attachment: UNNotificationAttachment? {
guard let attachmentURL = content.userInfo["image"] as? String, let imageData = try? Data(contentsOf: URL(string: attachmentURL)!) else {
return nil
}
return try? UNNotificationAttachment(data: imageData, options: nil)
}
}
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)
}
}
cURL to send push notification via Firebase/FCM
curl --location --request POST 'https://fcm.googleapis.com/fcm/send' \
--header 'Authorization: key=<YOUR FCM KEY>' \
--header 'Content-Type: application/json' \
--data-raw '{
"to": "fcm token for device or channel id",
"content_available": true,
"mutable_content": true,
"notification": {
"title": "Notification With Image",
"mutable-content": true,
"body": "Test Message "
},
"data": {
"image": "https://upload.wikimedia.org/wikipedia/commons/1/16/HDRI_Sample_Scene_Balls_%28JPEG-HDR%29.jpg"
}
}'