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.
Related
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?
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.
I am trying to create iOS Rich Push notifications with Xcode, Swift3.
I already make sure about push notifications (subject, body) with curl command of php but I can't create Rich Push Notifications referred to in this document.
I added Notification Service Extension like this: 「 File 」→「 New 」→「 Target... 」→「 Notification Service Extension 」 and also I added in 「'mutable_content': True」 curl command.
Then run but not call 「class NotificationService: UNNotificationServiceExtension」 so can't view push notifications image.
The following my code
import UserNotifications
class NotificationService: UNNotificationServiceExtension {
let imageKey = AnyHashable("gcm.notification.image_url")
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 imageUrl = request.content.userInfo[imageKey] as? String {
let session = URLSession(configuration: URLSessionConfiguration.default)
let task = session.dataTask(with: URL(string: imageUrl)!, completionHandler: { [weak self] (data, response, error) in
if let data = data {
do {
let writePath = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("push.png")
try data.write(to: writePath)
guard let wself = self else {
return
}
if let bestAttemptContent = wself.bestAttemptContent {
let attachment = try UNNotificationAttachment(identifier: "nnsnodnb_demo", url: writePath, options: nil)
bestAttemptContent.attachments = [attachment]
contentHandler(bestAttemptContent)
}
} catch let error as NSError {
print(error.localizedDescription)
guard let wself = self else {
return
}
if let bestAttemptContent = wself.bestAttemptContent {
contentHandler(bestAttemptContent)
}
}
} else if let error = error {
print(error.localizedDescription)
}
})
task.resume()
} else {
if let bestAttemptContent = bestAttemptContent {
contentHandler(bestAttemptContent)
}
}
}
override func serviceExtensionTimeWillExpire() {
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
contentHandler(bestAttemptContent)
}
}
}
REF : https://www.pluralsight.com/guides/swift/creating-ios-rich-push-notifications
I have done like this hope it helps for GIF images you can change extension to .png.
Make sure that in APNS payload attachment-url is coming for image.
Check for App transport security key in case of image url start from http://...
Your image should be under ~200px. For me it not works beyond(HIT and TRIAL).
Code:
import UserNotifications
import SDWebImage
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)
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()
}
SDWebImageDownloader.shared().downloadImage(with: URL(string: attachmentURL)!,
options: SDWebImageDownloaderOptions.continueInBackground,
progress: nil) { (image, data, error, flag) in
guard let attachment = UNNotificationAttachment.create(imageFileIdentifier: "image.gif",
data: data! as NSData,
options: nil) else { return failEarly() }
content.attachments = [attachment]
contentHandler(content.copy() as! UNNotificationContent)
if let bestAttemptContent = self.bestAttemptContent {
bestAttemptContent.title = "\(bestAttemptContent.title) [modified]"
contentHandler(bestAttemptContent)
}
}
}
override func serviceExtensionTimeWillExpire() {
if let contentHandler = contentHandler, let bestAttemptContent = bestAttemptContent {
contentHandler(bestAttemptContent)
}
}
}
extension UNNotificationAttachment {
static func create(imageFileIdentifier: String, data: NSData, options: [NSObject : AnyObject]?) -> 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
}
}
I'm using a feature which was announced by Apple in iOS 10 last year. I have the issue that the image in my notification is sometimes empty.
This is my UNNotificationServiceExtension.. I'm not really sure what I'm doing wrong. The images have a small size of max 1 MB. My payload from the server are correctly.
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 {
// Setting the category associated with the notification
if let category = bestAttemptContent.userInfo["category"] as? String {
bestAttemptContent.categoryIdentifier = category
}
// Fetching luubra if available
if let attachmentString = bestAttemptContent.userInfo["image"] as? String,
let attachmentUrl = URL(string: attachmentString) {
let session = URLSession(configuration: URLSessionConfiguration.default)
let attachmentDownloadTask = session.downloadTask(with: attachmentUrl,
completionHandler: { url, _, error in
if let error = error {
print("Error downloading notification image: \(error)")
} else if let url = url {
do {
let attachment = try UNNotificationAttachment(identifier: attachmentString,
url: url,
options: [UNNotificationAttachmentOptionsTypeHintKey: kUTTypeJPEG])
bestAttemptContent.attachments = [attachment]
} catch let e {
print("Error creating NotificationAttachment: \(e)")
}
}
print("Remote notification content: \(bestAttemptContent)")
contentHandler(bestAttemptContent)
})
attachmentDownloadTask.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)
}
}
}
It seems Apple will set the category associated directly like the title or body content.
It's important to save the media temporary to the disk. Because the iOS need some time to download the media file. iOS will handle the rest.
This works know awesome for me.
class NotificationService: UNNotificationServiceExtension {
var contentHandler: ((UNNotificationContent) -> Void)?
var bestAttemptContent: UNMutableNotificationContent?
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: #escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
self.bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
func failed() {
contentHandler(request.content)
}
guard let contentHandler = self.contentHandler, let bestAttemptContent = self.bestAttemptContent else {
return failed()
}
// Get the image from the User Payload
guard let imageURLString = request.content.userInfo["image"] as? String else {
return failed()
}
guard let imageURL = URL(string: imageURLString) else {
return failed()
}
// Download the Image Async
URLSession.shared.downloadTask(with: imageURL) { (path, _, error) in
if let error = error {
print(error.localizedDescription)
}
if let path = path {
// Save the image temporary to the disk
let tmpDirectory = NSTemporaryDirectory()
let tmpFile = "file://".appending(tmpDirectory).appending(imageURL.lastPathComponent)
guard let tmpURL = URL(string: tmpFile) else { return }
try? FileManager.default.moveItem(at: path, to: tmpURL)
// Add the attachment to the notification content
if let attachment = try? UNNotificationAttachment(identifier: "", url: tmpURL) {
bestAttemptContent.attachments = [attachment]
}
}
// Serve the notification content
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)
}
}
}
I am trying to implement the rich remote notifications in ios 10. I have implemented this code. The control after receiving the notification is going here, but I don't know how to download the image and display in the notifications. Thanks in advance.
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...
//print("title for image = \(bestAttemptContent.title)")
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)
}
}
}
you will get an attachment in your notificationData like this
"attachment-url": "https://yourimage.png"
and this is how you can use it
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()
}
}
Referenced from here
Finally its working. The issue here was that I had to add
NSAppTransportSecurity
tag in plist of the extension. After adding this tag, it started displaying the images. Hope it helps someone.