Image push notification is not working IOS - ios

UNNotificationServiceExtension is not getting called when i am trying to call the following custom payload data.
{
"aps": {
"alert": {
"title": "Hello",
"body": "body.."
},
"mutable-content":1,
"sound": "default",
"badge": 1,
},
"data": {
"attachment-url": "https://pusher.com/static_logos/320x320.png"
},
}
But when i call this below custom payload data,then UNNotificationServiceExtension is getting called
{
"aps": {
"alert": {
"title": "Hello",
"body": "body.."
},
"mutable-content":1,
"sound": "default",
"badge": 1,
},
"data": {
"attachment-url": ""
},
}
My problem is that if i put any http url in attachment-url key then UNNotificationServiceExtension it's not getting called but if i put sometext or leave it as empty instead of Url then it's will call.
I am sending custom payload from https://pushtry.com/
Here is my NotificationServiceExtension class
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()
}
} }
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 = 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

Add site with content blocker extension

Hi I am writing a content blocker app.
In this app I want to allow the user add a website that he wants to block.
How can i do that? I used SFContentBlockerManager.reloadContentBlocker(withIdentifier: blockerIdentifier) but it's just activate filter of block list
Thats my protocol on which i write domain(website) how can implement it here to my blocklist in content blocker extension
extension WhiteBlackViewController: AddDomainViewControllerDelegate {
func addDomain(text: String?) {
if listSwitch.isOn {
viewModel.items.append(text ?? "")
viewModel.filtered.append(text ?? "")
tableView.reloadData()
}
}
}
Thats my content blocker:
class ContentBlockerRequestHandler: NSObject, NSExtensionRequestHandling {
func beginRequest(with context: NSExtensionContext) {
let documentFolder = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.cyberGuard")
guard let jsonURL = documentFolder?.appendingPathComponent("whiteList.json") else { return }
let attachment = NSItemProvider(contentsOf: jsonURL)
let item = NSExtensionItem()
item.attachments = [attachment!]
context.completeRequest(returningItems: [item], completionHandler: nil)
}
}
So i already has a solution for this.
You just need create appGroup between your extension and your app
And after that write in another file json and activate it
func activateFilterBlock(fileName: String, website: String, realPath: String) {
viewModel.dictionary.append(["action": ["type": "block"], "trigger": ["url-filter": "\(website)"]])
let jsonData = try! JSONSerialization.data(withJSONObject: viewModel.dictionary, options: .prettyPrinted)
if let json = String(data: jsonData, encoding: String.Encoding.utf8) {
let file = "\(fileName).json"
if let dir = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.cyberGuard") {
let path = dir.appendingPathComponent(file)
do {
try json.write(to: path, atomically: false, encoding: String.Encoding.utf8)
let id = "\(realPath).json"
SFContentBlockerManager.reloadContentBlocker(withIdentifier: id) {error in
guard error == nil else {
print(error ?? "Error")
return
}
print("Reloaded")
}
} catch {
print(error.localizedDescription)
}
}
}
}
If you want receive this json you just can get it from new created json file
func getJSON(fileName: String, success: #escaping(Success), onError: #escaping(OnError)) {
let groupUrl: URL = FileManager().containerURL(forSecurityApplicationGroupIdentifier: "group.cyberGuard")!
if let path = URL(string: "\(fileName).json", relativeTo: groupUrl) {
do {
let data = try Data(contentsOf: path)
let value = try JSONDecoder().decode(WhiteBlackList.self, from: data)
items = value.map({ $0.trigger.urlFilter })
filtered = value.map({ $0.trigger.urlFilter })
if let json = try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [[String: [String: Any]]] {
dictionary = json
}
success()
} catch {
onError(error.localizedDescription)
}
}
}
If it helps, let me know :)

if i want to receive data from the server the app does not go to the URLSession if debug hem then skip the URLSession and i don't know why

This is the Json object which I should receive
[
{
"id": "18",
"profileName": "testProfiel24"
},
{
"id": "19",
"profileName": "testProfiel25"
},
{
"id": "1021",
"profileName": "testProfiel26"
},
{
"id": "1022",
"profileName": "testProfiel27"
},
{
"id": "1023",
"profileName": "testProfiel28"
}
]
This the function that I send, I used my api url to send an api call
func getData(){
let getToken : String? = KeychainWrapper.standard.string(forKey: "accessToken")
I add a value of the token to the url and with the print line I get the link
let url = URL(string: "http://....../profile/load")!
let queryItems = [URLQueryItem(name: "token", value: getToken!)]
let newUrl = url.appending(queryItems)!
print(newUrl)
The I make a URLSession with the new URL
but when hi goed to make dataTask. hi skips every things and goes to
resume()
URLSession.shared.dataTask(with: newUrl, completionHandler: { data, response, error in
guard let data = data,error == nil else{
print(error?.localizedDescription as Any)
return
}
var result : Profile?
do{
result = try JSONDecoder().decode(Profile.self, from: data)
}catch{
print("failed to convert\(error.localizedDescription)")
}
guard let json = result else{
return
}
print(json.id)
print(json.profileName)
}).resume()
with the debug mode I get this value of dataTask =
LocalDataTask <1F6380AD-5D01-490D-BA4A-94A7FD893531>.<2>
But I don't receive any data for the server because hi skips it
This the code to add a key and a value to the link and it works good
extension URL {
func appending(_ queryItems: [URLQueryItem]) -> URL? {
guard var urlComponents = URLComponents(url: self, resolvingAgainstBaseURL: true) else {
return nil
}
urlComponents.queryItems = (urlComponents.queryItems ?? []) + queryItems
return urlComponents.url
}
}
This the data that should receive for the server
struct Profile : Decodable {
let id, profileName : String
}

Incoming rich push notifications won't show the image. Swift 4

I'm trying to implement rich push notifications but I don't get the image at passed url to get displayed. When I open the notification I can only see title, subtitle and body. I'm following the tutorial at https://www.pluralsight.com/guides/creating-ios-rich-push-notifications as I found it suggested in other posts but I'm stuck at showing the image. I also read in one post that I shouldn't run the app but the NotificationExtension but when I do the app crashes when receiving the notification with
Message from debugger: Terminated due to signal 9 Program ended with
exit code: 0
message.
This is the print of userInfo when running the app instead :
[AnyHashable("attachment-url"):
https://firebasestorage.googleapis.com/v0/b/fix-it-b4b00.appspot.com/o/Prodotti%2FPrato.jpeg?alt=media&token=5d0fde09-2b86-45b0-a383-a11e7e8e241c,
AnyHashable("gcm.message_id"): 1560360808567562,
AnyHashable("productId"): 9290CEBE-393C-4285-BE7B-B9E2968A1AA0,
AnyHashable("aps"): {
alert = {
body = "Nuova promozione per articolo: Prato";
subtitle = "Negozio: vincenzo calia";
title = "Promozione negozio";
};
"content-available" = 1;
"mutable-content" = 1;
sound = true; }, AnyHashable("price"): 10.00, AnyHashable("gcm.notification.priority"): high,
AnyHashable("google.c.a.e"): 1]
I checked and the url is correct.
Could the problem be that the url is not in string format? I put a print to check it but I don't even get the print at beginning of 'didReceive' method. Can you see where is possibly going wrong?
As always Many thanks for your interest and time.
These are the function in NotificationService.swift :
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: #escaping (UNNotificationContent) -> Void) {
print("NotificationService: dide receive called")
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 apnsData = content.userInfo["data"] as? [String: Any] else {
return failEarly()
}
guard let attachmentURL = apnsData["attachment-url"] as? String else {
print("url is not in string form")
return failEarly()
}
guard let imageData = NSData(contentsOf:NSURL(string: attachmentURL)! as URL) else { return failEarly() }
guard let attachment = UNNotificationAttachment.create(imageFileIdentifier: "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 {
static func create(imageFileIdentifier: String, data: NSData, options: [NSObject : AnyObject]?) -> 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
}
}
And this is function that sends the push notification:
static func sendTopicPushNotification(to topic: String, title: String, subtitle: String, body: String, dataUrl: String, productId: String, price: String) {
let serverKey = firebaseServerKey // AAAA8c3j2...
// let topic = "/topics/<your topic here>" // replace it with partnerToken if you want to send a topic
let url = NSURL(string: "https://fcm.googleapis.com/fcm/send")
let postParams: [String : Any] = [
"to": topic,
// "priority": "high",
// "content_available": true,
// "mutable_content": true,
"notification": [
// "badge" : 1, sendig the badge number, will cause aglitch
"body": body,
"title": title,
"subtitle": subtitle,
"text": "some text",
"sound" : true, // or specify audio name to play
"priority": "high",
"content_available": true,
"mutable_content": true
// "category" : "pushNotificationCategory" // "topicPushNotification"
// "click_action" : "🚀", // action when user click notification (categoryIdentifier)
],
"data" : [
"attachment-url": dataUrl,
"productId": productId,
"price": price
]
]
let request = NSMutableURLRequest(url: url! as URL)
request.httpMethod = "POST"
request.setValue("key=\(serverKey)", forHTTPHeaderField: "Authorization")
request.setValue("application/json; charset=utf-8", forHTTPHeaderField: "Content-Type")
do {
// request.httpBody = try JSONSerialization.data(withJSONObject: postParams, options: JSONSerialization.WritingOptions())
request.httpBody = try JSONSerialization.data(withJSONObject: postParams, options: [.prettyPrinted]) // working
print("sendTopicPushNotification : My paramaters: \(postParams)")
} catch {
print("sendTopicPushNotification : Caught an error: \(error)")
}
let task = URLSession.shared.dataTask(with: request as URLRequest) { (data, response, error) in
if let realResponse = response as? HTTPURLResponse {
if realResponse.statusCode != 200 {
print("sendTopicPushNotification : Not a 200 response : \(realResponse)")
}
print("sendTopicPushNotification : response : \(realResponse)")
}
if let postString = NSString(data: data!, encoding: String.Encoding.utf8.rawValue) as String? {
print("sendTopicPushNotification : POST: \(postString)")
}
}
task.resume()
}
I'm finally able to show the picture with the notification. It might had to do with the fact that the tutorial I'm following is for swift 5, and I'm on swift 4.
I changed the didReceivecode and omitted the extension UNNotificationAttachmentcode and it's now displaying it correctly.
Next step is to add actions based on the category received in the payload, but I still have to figure out if that is possible without implementing also a Notification Content Extension, which for what I understood should be only to have a custom view to display the notification. Any hint will be very appreciated.
I hope this will help others as tutorials I found on the subject are a bit confusing and misleading at times. Also many thanks for up voting to the question.
NotificationServiceExtension didReceiveis now:
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: #escaping (UNNotificationContent) -> Void) {
print("NotificationService: dide receive called")
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
if let bestAttemptContent = bestAttemptContent {
if let urlString = bestAttemptContent.userInfo["attachment-url"] as? String,
let data = NSData(contentsOf: URL(string:urlString)!) as Data? {
let path = NSTemporaryDirectory() + "attachment"
_ = FileManager.default.createFile(atPath: path, contents: data, attributes: nil)
do {
let file = URL(fileURLWithPath: path)
let attachment = try UNNotificationAttachment(identifier: "attachment", url: file,options:[UNNotificationAttachmentOptionsTypeHintKey : "public.jpeg"])
bestAttemptContent.attachments = [attachment]
} catch {
print(error)
}
}
contentHandler(bestAttemptContent)
}

Record audio and send it to server in Swift 3

I have recorded audio with iOS successfully, but the problem is that I cannot send audio to server. Code that I tried is as shown below.
let audioData : NSData = try Data(contentsOf: (audioRecorder?.url)!) as NSData
var finalurl = url+Access_Token!
let params = [
"name":"iosTest.mp3",
"file": audioData] as [String : Any]
let manager = Alamofire.SessionManager.default
manager.session.configuration.timeoutIntervalForRequest = 30000
manager.request(finalurl, method: .post, parameters: params).responseJSON {
response in
stopActivityIndicator()
if let result = response.result.value {
let JSON = result as! NSDictionary
print(JSON)
let ResponseSuccess = JSON.object(forKey: "response")!
displayAlertMessage(userMessage: ResponseSuccess as! String, myView: self)
}
}
} catch let error as NSError {
print("audioPlayer error: \(error.localizedDescription)")
displayAlertMessage(userMessage: "Something error Occured! Please try again" as! String, myView: self)
}
Have you tried this from their official documentation on GitHub? Uploading Multipart Form Data
Alamofire.upload(
multipartFormData: { multipartFormData in
multipartFormData.append(audioRecorder?.url, withName: "iosTest.mp3")
},
to: "https://yourLinkGoesHere",
encodingCompletion: { encodingResult in
switch encodingResult {
case .success(let upload, _, _):
upload.responseJSON { response in
debugPrint(response)
}
case .failure(let encodingError):
print(encodingError)
}
}
)
or this Uploading a File
Alamofire.upload(audioRecorder?.url, to: "https://yourLinkGoesHere").responseJSON { response in
debugPrint(response)
}
SWIFT 5, Alamofire 5.
File with the request and completion #escaping :
import Alamofire
import SwiftyJSON
func request(variable1: String?, variable2: String?, audioFilePath: URL, completion: #escaping (String) -> ()) {
let url = URL(string: "https://...")!
let headers: HTTPHeaders = [
"content-type": "multipart/form-data; boundary=---011000010111000001101001",
"accept": "application/json",
"authorization": "Bearer \(your_token_if_needed)"
]
var parameters: [String: Any] = [:]
if variable1 != nil { parameters["var1"] = variable1 }
if variable2 != nil { parameters["var2"] = variable2 }
// Let's check the data
print(parameters)
AF.upload(multipartFormData: { multipartFormData in
for (key,value) in parameters {
multipartFormData.append((value as! String).data(using: .utf8)!, withName: key)
}
multipartFormData.append(audioFilePath, withName: "audio.m4a")
}, to: url, headers: headers)
.responseJSON { response in
switch response.result {
case .success:
do{
let json = try JSON(data: response.data!)
print(json)
// Parse an Array
let statusJson = json["status"].string
if statusJson == "success" {
completion("success")
}
else
{ completion(jsonErrorMsg!) }
} catch {
print(error.localizedDescription)
}
case .failure(let encodingError):
print("error:\(encodingError)")
}
}
}
Your ViewController class (Where you want to record the audio file)
// 1 - Delegation
class ViewController: UIViewController, AVAudioRecorderDelegate {
// 2 - Variables
var audioRecorder: AVAudioRecorder!
var audioFilename: URL!
var meterTimer:Timer!
var isAudioRecordingGranted: Bool!
override func viewDidLoad() {
super.viewDidLoad()
// MARK: - permission
switch AVAudioSession.sharedInstance().recordPermission {
case AVAudioSessionRecordPermission.granted:
isAudioRecordingGranted = true
audioRecorderAction()
break
case AVAudioSessionRecordPermission.denied:
isAudioRecordingGranted = false
break
case AVAudioSessionRecordPermission.undetermined:
AVAudioSession.sharedInstance().requestRecordPermission() { [unowned self] allowed in
DispatchQueue.main.async {
if allowed {
self.isAudioRecordingGranted = true
self.audioRecorderAction()
} else {
self.isAudioRecordingGranted = false
}
}
}
break
default:
break
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
audioRecorder = nil
}
func audioRecorderAction(){
if isAudioRecordingGranted {
//Create the session.
let session = AVAudioSession.sharedInstance()
do {
//Configure the session for recording and playback.
try session.setCategory(.playAndRecord, mode: .default, options: [])
try session.setActive(true)
//Set up a session
let settings = [
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVSampleRateKey: 44100,
AVNumberOfChannelsKey: 2,
AVEncoderAudioQualityKey: AVAudioQuality.medium.rawValue
]
//file name URL
audioFilename = getDocumentsDirectory().appendingPathComponent("audio.m4a")
//Create the audio recording, and assign ourselves as the delegate
audioRecorder = try AVAudioRecorder(url: audioFilename, settings: settings)
audioRecorder.delegate = self
audioRecorder.isMeteringEnabled = true
audioRecorder.record()
meterTimer = Timer.scheduledTimer(timeInterval: 1.0, target:self, selector:#selector(self.updateAudioTimer(timer:)), userInfo: nil, repeats: true)
}
catch let error {
print("Error for audio record: \(error.localizedDescription)")
}
}
}
func finishAudioRecording(success: Bool) {
audioRecorder.stop()
audioRecorder = nil
meterTimer.invalidate()
if success {
print("Finished.")
} else {
print("Failed :(")
}
}
#objc func updateAudioTimer(timer: Timer) {
if audioRecorder.isRecording {
let min = Int(audioRecorder.currentTime / 60)
let sec = Int(audioRecorder.currentTime.truncatingRemainder(dividingBy: 60))
let totalTimeString = String(format: "%02d:%02d", min, sec)
recordingTimeLabel.text = totalTimeString
audioRecorder.updateMeters()
if (sec == 10) {
finishAudioRecording(success: true)
}
}
}
func getDocumentsDirectory() -> URL {
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentsDirectory = paths[0]
return documentsDirectory
}
//MARK:- Audio recoder delegate methods
func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool) {
if !flag {
finishAudioRecording(success: false)
request(variable1: var_to_send1, variable2: var_to_send2, audioFilePath: audioFilename, completion: { (response) -> Void in
if response == "success" {
print("success")
} else {
print("error")
}
})
}
}

Media Attachment in iOS 10 Push Notifications

I'm struggling with adding an image to my Push Notification in iOS 10.
I have added a Notification Service Extension, and have used the following code:
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: #escaping (UNNotificationContent) -> Void) {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
if let urlString = request.content.userInfo["image-url"] as? String, let fileUrl = URL(string: urlString) {
URLSession.shared.downloadTask(with: fileUrl) { (location, response, error) in
if let location = location {
let options = [UNNotificationAttachmentOptionsTypeHintKey: kUTTypePNG]
if let attachment = try? UNNotificationAttachment(identifier: "", url: location, options: options) {
self.bestAttemptContent?.attachments = [attachment]
}
}
self.contentHandler!(self.bestAttemptContent!)
}.resume()
}
}
I got this code from the first answer below.
The issue I'm having now is that the notification is received, with a short delay which indicates the download must be happening, but there is no attachment shown.
I am assuming that serviceExtensionTimeWillExpire() is being called and just showing the bestAttempt
Any help is greatly appreciated.
I have my APNs payload configured correctly, I believe:
apns: {
aps: {
alert: {
title: "Title",
subtitle: "Subtitle",
body: "Body"
},
"mutable-content": 1
},
"image-url": "https://helloworld.com/image.png"
}
You have to pull the url out of the notification's user info, then download the image and give a file url to the attachment. Try something like:
if let urlString = request.content.userInfo["image-url"] as? String, let fileUrl = URL(string: urlString) {
URLSession.shared.downloadTask(with: fileUrl) { (location, response, error) in
if let location = location {
let options = [UNNotificationAttachmentOptionsTypeHintKey: kUTTypePNG]
if let attachment = try? UNNotificationAttachment(identifier: "", url: location, options: options) {
self.bestAttemptContent.attachments = [attachment]
}
}
self.contentHandler(self.bestAttemptContent)
}.resume()
}
I've managed to work this out with the following code:
Swift:
if let PusherNotificationData = request.content.userInfo["data"] as? NSDictionary {
if let urlString = PusherNotificationData["image-url"] as? String, let fileUrl = URL(string: urlString) {
URLSession.shared.downloadTask(with: fileUrl) { (location, response, error) in
if let location = location {
let options = [UNNotificationAttachmentOptionsTypeHintKey: kUTTypePNG]
if let attachment = try? UNNotificationAttachment(identifier: "", url: location, options: options) {
self.bestAttemptContent?.attachments = [attachment]
}
}
self.contentHandler!(self.bestAttemptContent!)
}.resume()
}
}
Node:
apns: {
aps: {
alert: {
title: "title",
subtitle: "subtitle",
body: "body"
},
"mutable-content": 1,
category: "test"
},
data: {
"image-url": "www.helloworld.com/image.png"
}
}
Thanks for your help!
Another solution (and a testable one) could be writing the image in a temporally location:
// NotificationRequestHandler
func getImageURL(from userInfo: [AnyHashable: Any]) throws -> URL {
// parse the image URL and return it, otherwise throws and error
}
func temporaryWriteData(from url: URL) throws -> (String, URL) {
let temporaryDirectoryUrl = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
let temporaryFileName = UUID().uuidString
let temporaryFileUrl = temporaryDirectoryUrl.appendingPathComponent(temporaryFileName)
let data = try Data(contentsOf: url)
try data.write(to: temporaryFileUrl, options: .atomic)
return (temporaryFileName, temporaryFileUrl)
}
and on didReceive(_:withContentHandler:):
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
defer {
contentHandler(bestAttemptContent ?? request.content)
}
do {
let handler = NotificationRequestHandler()
let imageUrl = try handler.getImageURL(from: request.content.userInfo)
let (imageFileName, imageFileUrl) = try handler.temporaryWriteData(from: imageUrl)
let attachment = try UNNotificationAttachment(identifier: imageFileName, url: imageFileUrl, options: [UNNotificationAttachmentOptionsTypeHintKey: "public.png"])
bestAttemptContent?.attachments = [attachment]
} catch {}
Also something very helpful to debug extensions
and how to test extentions

Resources