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
}
}
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 need to implement rich notification in current application with image.
I have implemented everything and its working fine. And receiving push notification, but getting only title and message but not image as attachments
Normal notifications without mutable-content work fine.
As far as I tested, there is no problem with certificate side.. I am using NotificationService extension to show media attachment
Any idea?
My payload looks like below:
[AnyHashable("title"): New trailer released- Hindi Movies, AnyHashable("message"): Kuchh Bheege Alfaaz - Official Hindi Trailer, AnyHashable("aps"): {
alert = "New trailer released- Hindi Movies";
badge = 2;
sound = default;
}, AnyHashable("trailer_img"): uploads/trailer_image/9703831856a7968b8b5cf6b10357dc2c1.jpg]
My NotificationService Class code like below:
class NotificationService: UNNotificationServiceExtension {
var contentHandler : ((UNNotificationContent) -> Void)?
var content : UNMutableNotificationContent?
override func didReceive(_ request: UNNotificationRequest,
withContentHandler contentHandler:
#escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
self.content = (request.content.mutableCopy()
as? UNMutableNotificationContent)
let userInfo : [AnyHashable: Any] = request.content.userInfo
print(userInfo)
if let bca = self.content {
func save(_ identifier: String,
data: Data, options: [AnyHashable: Any]?)
-> UNNotificationAttachment? {
let directory = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(ProcessInfo.processInfo.globallyUniqueString,
isDirectory: true)
do {
try FileManager.default.createDirectory(at: directory,
withIntermediateDirectories: true,
attributes: nil)
let fileURL = directory.appendingPathComponent(identifier)
try data.write(to: fileURL, options: [])
return try UNNotificationAttachment.init(identifier: identifier,
url: fileURL,
options: options)
} catch {
}
return nil
}
func exitGracefully(_ reason: String = "") {
let bca = request.content.mutableCopy()
as? UNMutableNotificationContent
bca!.title = reason
contentHandler(bca!)
}
let reachability = Reachability()!
reachability.whenReachable = { reachability in
DispatchQueue.main.async {
if reachability.isReachableViaWiFi {
guard let content = (request.content.mutableCopy()
as? UNMutableNotificationContent) else {
return exitGracefully()
}
let userInfo : [AnyHashable: Any] = request.content.userInfo
print(userInfo)
guard let attachmentURL = userInfo["trailer_img"]
as? String else {
return exitGracefully()
}
guard let imageData =
try? Data(contentsOf: URL(string: "http://techindiana.com/dev/truetrailer_app/" + attachmentURL)!)
else {
return exitGracefully()
}
guard let attachment =
save("image.png", data: imageData, options: nil)
else {
return exitGracefully()
}
content.attachments = [attachment]
contentHandler(content.copy() as! UNNotificationContent)
} else {
return exitGracefully()
}
}
}
reachability.whenUnreachable = { reachability in
DispatchQueue.main.async {
return exitGracefully()
}
}
do {
try reachability.startNotifier()
} catch {
return exitGracefully()
}
}
}
override func serviceExtensionTimeWillExpire() {
if let contentHandler = contentHandler, let bca = self.content {
contentHandler(bca)
}
}
}
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.
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 added the UNNotificationServiceExtension to the application, I successfully get the data, and I load the image that comes in the data to push notification, but I can not display this image in the push notification window. Please tell me how it can be fixed?
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"
contentHandler(bestAttemptContent)
let attachmentStorage = AttachmentStorage()
if let imagePath = request.content.userInfo["image"] as? String {
guard let url = URL(string: imagePath) else {
contentHandler(bestAttemptContent)
return
}
debugPrint("url", url)
attachmentStorage.store(url: url) { (localURL, error) in
if let localURL = localURL {
debugPrint("path", localURL)
do {
let attachment = try UNNotificationAttachment.init(identifier: "image", url: localURL, options: nil)
bestAttemptContent.attachments = [attachment]
debugPrint("bestAttemptContent.attachments", bestAttemptContent.attachments, bestAttemptContent.attachments.count)
contentHandler(bestAttemptContent)
return
} catch {
contentHandler(bestAttemptContent)
return
}
}
}
}
contentHandler(bestAttemptContent)
}
}
class AttachmentStorage {
func store(url: URL, completion: ((URL?, Error?) -> ())?) {
// obtain path to temporary file
let filename = ProcessInfo.processInfo.globallyUniqueString
let path = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("\(filename).jpeg")
// fetch attachment
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
do {
let _ = try data?.write(to: path)
completion?(path, error)
} catch {
debugPrint(error.localizedDescription)
completion?(nil, error)
}
}
task.resume()
}
}
Logs
"bestAttemptContent.attachments" [<UNNotificationAttachment: 0x14dd626c0; identifier: image, family: Image, URL: file:///private/var/mobile/Containers/Data/PluginKitPlugin/1F13F774-B6CA-4567-9EF0-EA61C9A1F2A0/tmp/23A8E7EC-29FB-4C36-893D-5B0DCFB711B2-13808-000002A699FBF67B.jpeg, type: public.jpeg, options: <UNImageNotificationAttachmentOptions: 0x14dd5afd0>>] 1
I added this code. And even if you remove contentHandler sometimes you get duplicates or there were no images
let request = UNNotificationRequest.init(identifier: "image", content: bestAttemptContent, trigger: nil)
UNUserNotificationCenter.current().add(request) { (error) in
// handle error
}
I made this and it works for me
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...
if let imagePath = request.content.userInfo["image"] as? String {
guard let url = URL(string: imagePath) else {
contentHandler(bestAttemptContent)
return
}
guard let imageData = NSData(contentsOf: url) else {
contentHandler(bestAttemptContent)
return
}
guard let attachment = UNNotificationAttachment.create(imageFileIdentifier: "image.jpg", data: imageData, options: nil) else {
print("error in UNNotificationAttachment.create()")
contentHandler(bestAttemptContent)
return
}
bestAttemptContent.attachments = [ attachment ]
}
contentHandler(bestAttemptContent)
}
}
extension UNNotificationAttachment {
/// Save the image to disk
static func create(imageFileIdentifier: String, data: NSData, options: [NSObject : AnyObject]?) -> UNNotificationAttachment? {
let fileManager = FileManager.default
let tmpSubFolderName = ProcessInfo.processInfo.globallyUniqueString
guard let tmpSubFolderURL = NSURL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(tmpSubFolderName, isDirectory: true) else { return nil }
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
}
}
here a working code. Also verify the certificates are valid. I recommend you the RayWenderlich Screencasts of ios10 Notifications.
Try to delete the contentHandler after you change the title and body and the lastone
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: #escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
if let bestAttemptContent = bestAttemptContent {
var attachmentString = ""
if let bigImage = bestAttemptContent.userInfo["gcm.notification.bigImage"] as? String {
attachmentString = bigImage
}
if attachmentString != "", let attachmentUrl = URL(string: attachmentString)
{
let session = URLSession(configuration: URLSessionConfiguration.default)
let attachmentDownloadTask = session.downloadTask(with: attachmentUrl, completionHandler: { (url, response, error) in
if let error = error {
print("Error downloading attachment: \(error.localizedDescription)")
} else if let url = url {
let attachment = try! UNNotificationAttachment(identifier: attachmentString, url: url, options: [UNNotificationAttachmentOptionsTypeHintKey : kUTTypePNG])
bestAttemptContent.attachments = [attachment]
}
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)
}
}