Images in iOS push notification - ios

I am trying to send images in push notifications
I have made the notifications registrations in app delegate and apns device token is generating properly.
ALso I have coded in service ext as follows:
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)
// Get the custom data from the notification payload
if let notificationData = request.content.userInfo["data"] as? [String: String] {
// Grab the attachment
if let urlString = notificationData["attachment-url"], let fileUrl = URL(string: urlString) {
// Download the attachment
URLSession.shared.downloadTask(with: fileUrl) { (location, response, error) in
if let location = location {
// Move temporary file to remove .tmp extension
let tmpDirectory = NSTemporaryDirectory()
let tmpFile = "file://".appending(tmpDirectory).appending(fileUrl.lastPathComponent)
let tmpUrl = URL(string: tmpFile)!
try! FileManager.default.moveItem(at: location, to: tmpUrl)
// Add the attachment to the notification content
if let attachment = try? UNNotificationAttachment(identifier: "", url: tmpUrl) {
self.bestAttemptContent?.attachments = [attachment]
}
}
// Serve the notification content
self.contentHandler!(self.bestAttemptContent!)
}.resume()
}
}
}
}
.
And the payload in json is as follows
{
"aps":
{"sound":"default","alert":
{"title":"iOS","body":"Hello Dude...."},
"mutable-content": 1},
"CustomData":
{"mType":"alert","m":"Hello Dude...."},
"Attachement-url":"https://pusher.com/static_logos/320x320.png"
}
I am receiving the title and message but image is not coming.
Please guide how to get image in push notifications

For Swift, If you want you can try with this framework
Also Add "content-available":1 in your aps
OR you can try downloading like this,
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: #escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as?UNMutableNotificationContent)
bestAttemptContent?.title = request.content.title
bestAttemptContent?.body = request.content.body
guard let content = (request.content.mutableCopy() as? UNMutableNotificationContent) else {
return failEarly()
}
guard let payload = content.userInfo["CustomData"] as? [String: Any] else {
return failEarly()
}
guard let attachmentURL = payload["Attachement-url"] as? String else {
return failEarly()
}
let identifierName = getIdentifierName(fileURL: attachmentURL)
let tmpSubFolderName = ProcessInfo.processInfo.globallyUniqueString
guard let imageData = NSData(contentsOf:NSURL(string: attachmentURL)! as URL) else { return failEarly() }
guard let attachment = UNNotificationAttachment.create(imageFileIdentifier: identifierName, data: imageData, options: nil, tmpSubFolderName: tmpSubFolderName) else { return failEarly() }
content.attachments = [attachment]
contentHandler(content.copy() as! UNNotificationContent)
}
}
func getIdentifierName(fileURL : String) -> String {
var identifierName : String = "image.jpg"
if !fileURL.isEmpty() {
identifierName = "file.\((fileURL as NSString).lastPathComponent)"
}
return identifierName
}
func failEarly() {
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
contentHandler(bestAttemptContent)
}
}
extension UNNotificationAttachment {
static func create(imageFileIdentifier: String, data: NSData, options: [NSObject : AnyObject]?, tmpSubFolderName : String) -> UNNotificationAttachment? {
let fileManager = FileManager.default
let tmpSubFolderName = ProcessInfo.processInfo.globallyUniqueString
let fileURLPath = NSURL(fileURLWithPath: NSTemporaryDirectory())
let tmpSubFolderURL = fileURLPath.appendingPathComponent(tmpSubFolderName, isDirectory: true)
do {
try fileManager.createDirectory(at: tmpSubFolderURL!, withIntermediateDirectories: true, attributes: nil)
let fileURL = tmpSubFolderURL?.appendingPathComponent(imageFileIdentifier)
try data.write(to: fileURL!, options: [])
let imageAttachment = try UNNotificationAttachment.init(identifier: imageFileIdentifier, url: fileURL!, options: options)
return imageAttachment
} catch let error {
print("error \(error)")
}
return nil
}
}

This line:
if let urlString = notificationData["attachment-url"], let fileUrl = URL(string: urlString) {
Is looking for an attachment-url value as a child of a data object in the userInfo dictionary.
It is looking for this:
{
"aps" : {
...
},
"data" : {
"attachment-url" : "some url"
}
}
But the payload in your question is this:
{
"aps":{
"sound":"default",
"alert": {
"title":"iOS",
"body":"Hello Dude...."
},
"mutable-content": 1
},
"CustomData": {
"mType":"alert",
"m":"Hello Dude...."
},
"Attachement-url":"https://pusher.com/static_logos/320x320.png"
}
The "data" section does not exist, and the attachment-url key does not exist.
Change your Swift code to match what is in the payload and you should be able to get the image URL and download it.
You will have a big problem if you receive a notification that does not have the attachment URL key or the attachment URL is not a properly formed URL. In those cases your if let will not be entered and the contentHandler will not be called! This will not just cause the service extension to lock up, but it will prevent any notification that does not have the attachment URL from being delivered! Add an else that calls the contentHandler to fix this.
Once you have it downloaded though there is another problem. iOS will need to know what kind of data you are putting in the attachment. The attachment options dictionary allows you to include type information about the attachment. Get the MIME Type of the downloaded file and create a Uniform Type Identifier from that. The Uniform Type Identifier string can then be used in the options dictionary.
I cover all of this in depth in the iOS Notifications book. The sample chapter available now deals with adding images to notifications.

Related

I'm making a rich push notifications in my iOS app but the notifications appeared without image why?

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...
bestAttemptContent.title = "\(bestAttemptContent.title) [modified]"
var urlString:String? = nil
if let urlImageString = request.content.userInfo["urlImageString"] as? String {
urlString = urlImageString
}
if urlString != nil, let fileUrl = URL(string: urlString!) {
print("fileUrl: \(fileUrl)")
guard let imageData = NSData(contentsOf: fileUrl) else {
contentHandler(bestAttemptContent)
return
}
guard let attachment = UNNotificationAttachment.saveImageToDisk(fileIdentifier: "image.jpg", data: imageData, options: nil) else {
print("error in UNNotificationAttachment.saveImageToDisk()")
contentHandler(bestAttemptContent)
return
}
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)
}
}
}
#available(iOSApplicationExtension 10.0, *)
extension UNNotificationAttachment {
static func saveImageToDisk(fileIdentifier: String, data: NSData, options: [NSObject : AnyObject]?) -> UNNotificationAttachment? {
let fileManager = FileManager.default
let folderName = ProcessInfo.processInfo.globallyUniqueString
let folderURL = NSURL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(folderName, isDirectory: true)
do {
try fileManager.createDirectory(at: folderURL!, withIntermediateDirectories: true, attributes: nil)
let fileURL = folderURL?.appendingPathComponent(fileIdentifier)
try data.write(to: fileURL!, options: [])
let attachment = try UNNotificationAttachment(identifier: fileIdentifier, url: fileURL!, options: options)
return attachment
} catch let error {
print("error \(error)")
}
return nil
}
}
I tried using more than one source, but the result is the same, the notification appears without a picture. What is the reason here? How can I solve the problem?

What is the right way to share the data downloaded in UNNotificationServiceExtension extension with UNNotificationContentExtension extension APNS

I download the image on receiving the notification in UNNotificationServiceExtension using the code
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]"
if let urlImageString = request.content.userInfo["image"] as? String, let url = URL(string: urlImageString) as? URL {
guard let imageData = NSData(contentsOf: url) else {
contentHandler(bestAttemptContent)
return
}
guard let attachment = UNNotificationAttachment.saveImageToDisk(fileIdentifier: "image.jpg", data: imageData, options: nil) else {
contentHandler(bestAttemptContent)
return
}
bestAttemptContent.attachments = [attachment]
}
contentHandler(bestAttemptContent)
}
}
I have a extension for UNNotificationAttachment to save the data to disk
#available(iOSApplicationExtension 10.0, *)
extension UNNotificationAttachment {
static func saveImageToDisk(fileIdentifier: String, data: NSData, options: [NSObject : AnyObject]?) -> UNNotificationAttachment? {
if let dir = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "some.group.identifier")
{
let fileURL = dir.appendingPathComponent("image.jpg", isDirectory: false)
do {
try data.write(to: fileURL, options: .atomicWrite)
let attachment = try UNNotificationAttachment(identifier: "image.jpg", url: fileURL, options: options)
return attachment
}
catch {
return nil
}
}
return nil
}
}
My app already uses Appgroups so I use one of the app group created by main app and share it with both UNNotificationServiceExtension and UNNotificationContentExtension
Finally I try to read data from url in UNNotificationContentExtension using
func didReceive(_ notification: UNNotification) {
let attachment = notification.request.content.attachments[0]
let fileURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "some.group.identifier")
let imageURL = fileURL?.appendingPathComponent("image.jpg")
if attachment.identifier == "image.jpg" {
let image = UIImage(contentsOfFile: attachment.url.absoluteString)
self.notificationImageView.image = image
}
}
I get the image as nil, on trying to find if file exists using FileManager.default.fileExists(atPath: imageURL!.path) it always returns false.
What am I doing wrong here? Please help
Try in this way
UNNotificationAttachment *attachment = [content.attachments objectAtIndex:0];
if (attachment.URL.startAccessingSecurityScopedResource) {
NSData *data = [NSData dataWithContentsOfURL:attachment.URL];
if (data) {
pagerView.imgViewProductImage.image = [UIImage imageWithData:data];
} else {
[attachment.URL stopAccessingSecurityScopedResource];
return [UIView new];
}
[attachment.URL stopAccessingSecurityScopedResource];
}

Rich notification with expanded image in notification tray

I'm trying to display rich notifications on my app with an expanded image like this. I have used Notification Service extension to implement this in the app.
But I'm only getting a thumbnail when I receive a notification, which looks something like this. The expanded image appears when when I long press on a 3D-touch capable phone, otherwise it just displays a thumbnail on phones which doesn't have 3D-touch.
I wasn't able to find any documentation or any questions on SO which explains how to do this if it is possible. I would like to know if it is possible to do this on iOS, if not is there any possible workaround to accomplish this? Here is my NotificationSerivce extension. Any help is much appreciated! Thanks!
class NotificationService: UNNotificationServiceExtension {
let fileManager = FileManager()
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...
guard let content = (request.content.mutableCopy() as? UNMutableNotificationContent) else {
return self.contentHandler = contentHandler
}
bestAttemptContent.title = "\(bestAttemptContent.title) [modified]"
guard let attachmentURL = content.userInfo["attachment-url"] as? String else {
return self.contentHandler = contentHandler
}
guard let fileName = attachmentURL.components(separatedBy: "/").last else {
return self.contentHandler = contentHandler
}
guard let imageData = try? Data(contentsOf: URL(string: attachmentURL)!) else {
return self.contentHandler = contentHandler
}
if let thumbnailAttachment = UNNotificationAttachment.create(imageFileIdentifier: fileName, data: imageData, options: nil) {
bestAttemptContent.attachments = [thumbnailAttachment]
}
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)
}
}
}
extension UNNotificationAttachment {
/// Save the image to disk
static func create(imageFileIdentifier: String, data: Data, options: [AnyHashable: Any]?) -> UNNotificationAttachment? {
let fileManager = FileManager.default
let tmpSubFolderName = ProcessInfo.processInfo.globallyUniqueString
let tmpSubFolderURL = NSURL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(tmpSubFolderName, isDirectory: true)
do {
try fileManager.createDirectory(at: tmpSubFolderURL!, withIntermediateDirectories: true, attributes: nil)
let fileURL = tmpSubFolderURL?.appendingPathComponent(imageFileIdentifier)
try data.write(to: fileURL!, options: [])
let imageAttachment = try UNNotificationAttachment(identifier: imageFileIdentifier, url: fileURL!, options: options)
return imageAttachment
} catch let error {
print("error \(error)")
}
return nil
}
}
It is a very old question and I guess you'd have discovered by now, what you are trying to achieve is not technically feasible.
The notifications appear in their collapsed form and only if the user 3d presses (or long-presses in case of devices without 3d-touch) on the notification will they be shown in the expanded form.

Rich Notification in swift. Image not show in notification

I make rich notification to show image in notification but whenever i send simple message then i get notification. Last 2 day i am trying to show image in notification but it was not done. Please help me to do this.
Thank you in advance
This is my code.
In Notification Service Extension
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: #escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
// Get the custom data from the notification payload
if let data = request.content.userInfo["data"] as? [String: String] {
// Grab the attachment
if let urlString = data["attachment-url"], let fileUrl = URL(string: urlString) {
// Download the attachment
URLSession.shared.downloadTask(with: fileUrl) { (location, response, error) in
if let location = location {
// Move temporary file to remove .tmp extension
let tmpDirectory = NSTemporaryDirectory()
let tmpFile = "file://".appending(tmpDirectory).appending(fileUrl.lastPathComponent)
let tmpUrl = URL(string: tmpFile)!
try! FileManager.default.moveItem(at: location, to: tmpUrl)
// Add the attachment to the notification content
if let attachment = try? UNNotificationAttachment(identifier: "", url: tmpUrl) {
self.bestAttemptContent?.attachments = [attachment]
}
}
// Serve the notification content
self.contentHandler!(self.bestAttemptContent!)
}.resume()
}
}
}
This is my notification structure
{
"aps" : {
"alert" : {
"title" : "Push Remote Rich Notifications",
"subtitle" : "iOS 10 - New API",
"body" : "Media Image Rich notification"
},
"mutable-content" : 1,
"category" : "imageIdentifier"
},
"data" : {
"attachment-url": "https://raw.githubusercontent.com/Sweefties/iOS10-NewAPI-UserNotifications-Example/master/source/iOS10-NewAPI-UserNotifications-Example.jpg"
}
}
Use this following code
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 else {
return
}
guard let attachmentUrlString = request.content.userInfo["pic_url"] as? String else {
return
}
guard let url = URL(string: attachmentUrlString) else {
return
}
URLSession.shared.downloadTask(with: url, completionHandler: { (optLocation: URL?, optResponse: URLResponse?, error: Error?) -> Void in
if error != nil {
print("Download file error: \(String(describing: error))")
return
}
guard let location = optLocation else {
return
}
guard let response = optResponse else {
return
}
do {
let lastPathComponent = response.url?.lastPathComponent ?? ""
var attachmentID = UUID.init().uuidString + lastPathComponent
if response.suggestedFilename != nil {
attachmentID = UUID.init().uuidString + response.suggestedFilename!
}
let tempDict = NSTemporaryDirectory()
let tempFilePath = tempDict + attachmentID
try FileManager.default.moveItem(atPath: location.path, toPath: tempFilePath)
let attachment = try UNNotificationAttachment.init(identifier: attachmentID, url: URL.init(fileURLWithPath: tempFilePath))
bestAttemptContent.attachments.append(attachment)
}
catch {
print("Download file error: \(String(describing: error))")
}
OperationQueue.main.addOperation({() -> Void in
self.contentHandler?(bestAttemptContent);
})
}).resume()
}
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)
}
}
also the following code in info.plist
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
add this key in NSExtension NSDictionary in Info.plist
<key>NSExtensionAttributes</key>
<dict/>

UNNotificationServiceExtension does't work with Firebase Cloud Functions

I integrated UNNotificationServiceExtension into the application, but I do not have additional content such as images. I also thought that I did not correctly implement the loading of pictures and therefore I tried to change the name and body payload for the test. But it did not give me any results. I implement Push Notification through Firebase console and through Firebase cloud funcitons. I also read several posts on this topic, but they did not help me. Please tell me how it can be fixed?
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 = "Apple [modified]"
bestAttemptContent.body = "Xcode"
let attachmentStorage = AttachmentStorage()
if let jpeg = request.content.userInfo["image"] as? String {
guard let url = URL(string: jpeg) else {
contentHandler(bestAttemptContent)
return
}
debugPrint("url", url)
attachmentStorage.store(url: url, extension: "jpeg") { (path, error) in
if let path = path {
do {
let attachment = try UNNotificationAttachment(identifier: "image", url: path, options: nil)
bestAttemptContent.attachments = [attachment]
contentHandler(bestAttemptContent)
return
} catch {
contentHandler(bestAttemptContent)
return
}
}
}
}
if let png = request.content.userInfo["png"] as? String {
guard let url = URL(string: png) else {
contentHandler(bestAttemptContent)
return
}
attachmentStorage.store(url: url, extension: "png") { (path, error) in
if let path = path {
do {
let attachment = try UNNotificationAttachment(identifier: "image", url: path, options: nil)
bestAttemptContent.attachments = [attachment]
contentHandler(bestAttemptContent)
return
} catch {
contentHandler(bestAttemptContent)
return
}
}
}
}
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)
}
}
}
class AttachmentStorage {
func store(url: URL, extension: String, completion: ((URL?, Error?) -> ())?) {
// obtain path to temporary file
let filename = ProcessInfo.processInfo.globallyUniqueString
let path = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("\(filename).\(`extension`)")
// fetch attachment
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
let _ = try! data?.write(to: path)
completion?(path, error)
}
task.resume()
}
}
Payload from cloud functions
const payload = {
notification: {
title: 'It’s decision time!',
body: 'text'
sound: 'default',
mutable_content: true
},
data: {
image: "https://example.com/static_logos/320x320.png"
}
};
Updated: I also read this issue
I fixed it.
I was sending a mutable_content as true. But oddly the Firebase engineers are expecting a String ie "true" instead
const payload = {
notification: {
title: 'It’s decision time!',
body: "text"
sound: 'default',
mutable_content: 'true'
},
data: {
image: "example"
}
};

Resources