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
Related
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 :)
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)
}
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
}
}
I am using swift to access the youtubeinmp3 api and I am stuck. I am quite new to this so please be gentle. Using the api I get a json response and use the link property that gets returned to download a file. However the link I get looks like this : http://www.youtubeinmp3.com/download/get/?i=PL6sPTHlt1KHYj6hUsDxW8zjAgcNiU1SXVHIzxnSALX8%2FKNV35SZqd9l5qxk7LOiD%2FcrlIUe5JJvgZxKg0WeMw%3D%3D
The link works fine in a browser like Chrome but the swift app downloads an unknown file, definitely not an mp3.
I used this question to find the code.
Any ideas on how I could go through the redirect to get to the mp3 download link?
Thank you in advance.
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let videoURL1 = (videosArray[indexPath.row]).objectForKey("videoID") as! String
//let videoURL = "https://www.youtube.com/watch?v=\(videoURL1)"
let videoURL = "//www.youtubeinmp3.com/fetch/?video=https://www.youtube.com/watch?v=\(videoURL1)"
let urlString = "http://www.youtubeinmp3.com/fetch/?format=JSON&video=\(videoURL)"
let url = NSURL(string: "http://www.youtubeinmp3.com/fetch/?format=JSON&video=\(videoURL)")
let sessionConfig = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: sessionConfig, delegate: nil, delegateQueue: nil)
let request = NSMutableURLRequest(URL: url!)
request.HTTPMethod = "GET"
let task = session.dataTaskWithRequest(request, completionHandler: { (data: NSData?, response: NSURLResponse?, error: NSError?) -> Void in
if (error == nil) {
if let response = response as? NSHTTPURLResponse {
print("response=\(response)")
if response.statusCode == 200 {
if data != nil {
do {
let responseJSON = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.AllowFragments) as! NSDictionary;
//let urlString = NSURL(string:"\(responseJSON["link"] as! String)")
//print("URLString: \(urlString)")
//let directDownloadURL = NSURL(string: urlString)
// Call your method loadFileAsync
// code here
let urlString = responseJSON["link"] as! String
//let finalURLString = urlString.stringByReplacingOccurrencesOfString("get", withString: "mp3")
print(urlString)
dispatch_async(dispatch_get_main_queue(), {
let identifier = NSUUID().UUIDString
let downloadItem = DownloadsTableViewCellItem(identifier: identifier, urlString: urlString, infoLabelTitle: selectedName, stateLabelTitle: "Press Download", progressLabelTitle: "", action: DownloadsTableViewCellAction.Download)
DownloadsViewController().addDownloadItem(downloadItem, withIdentifier: identifier)
SVProgressHUD.showSuccessWithStatus("Download Added")
print("Download Task Added")
})
}
catch let JSONError as NSError {
print("\(JSONError)")
}
catch {
print("unknown error in JSON Parsing");
}
}
}
}
}
else {
print("Failure: \(error!.localizedDescription)");
}
})
task.resume()
}
I have a function which parses JSON, but I get a nil error dealing with the URL strings:
var jsonResponse: NSMutableDictionary?
do{
jsonResponse = try NSJSONSerialization.JSONObjectWithData(data!,
options: NSJSONReadingOptions.AllowFragments) as? NSMutableDictionary;
let info : NSArray = jsonResponse!.valueForKey("latest_receipt_info") as! NSArray
let transaction_id: String? = info[0].valueForKey("transaction_id") as? String
let purchase_date: String? = info[0].valueForKey("purchase_date") as? String
let product_id: String? = info[0].valueForKey("product_id") as? String
let web_order_line_item_id: String? = info[0].valueForKey("web_order_line_item_id") as? String
print("test")
// Send Values
let addIAPUrl:NSString = "http://bla.com/application/addIAP.php?transaction_id=\(transaction_id!)&purchase_date=\(purchase_date)&product_id=\(product_id)&web_order_line_item_id=\(web_order_line_item_id)&userID=\(prefs.valueForKey("userID") as! String!)"
self.apiRequests(addIAPUrl as String, completionHandler: { (success, message) -> Void in
print("success \(addIAPUrl)")
if(success == 1){
dispatch_async(dispatch_get_main_queue()){
// ADDED
print("success \(addIAPUrl)")
}
}else{
// DONT ADDED
}
})
The output doesn't return any error but the function fails after print("test"). The apiRequests function works in other cases, but doesn't seem to work in this context.
I would appreciate any help finding the problem.
Here is the code for the apiRequest function:
func apiRequests(url : String, completionHandler : ((success : Int, message : String) -> Void)) {
guard let url = NSURL(string: url as String) else {
return
}
let urlRequest = NSURLRequest(URL: url)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task = session.dataTaskWithRequest(urlRequest, completionHandler: { (data, response, error) in
guard let responseData = data else {
return
}
guard error == nil else {
print(error)
return
}
let post: NSDictionary
do {
post = try NSJSONSerialization.JSONObjectWithData(responseData,
options: []) as! NSDictionary
} catch {
return
}
let numberFromString = Int((post["success"] as? String)!)
completionHandler(success: (numberFromString)!, message: (post["message"] as? String)!)
})
task.resume()
}
It seems to me that the problem is most likely that your apiRequests: function is erroring at one of many places, and is returning instead of calling your callback with an error state.
func apiRequests(url : String, completionHandler : ((success : Int, message : String) -> Void)) {
guard let url = NSURL(string: url as String) else {
completionHandler(0, "Couldn't get URL")
return
}
let urlRequest = NSURLRequest(URL: url)
let config = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: config)
let task = session.dataTaskWithRequest(urlRequest, completionHandler: { (data, response, error) in
guard let responseData = data else {
completionHandler(0, "Data was nil")
return
}
guard error == nil else {
print(error)
completionHandler(0, "Error wasn't nil")
return
}
let post: NSDictionary
do {
post = try NSJSONSerialization.JSONObjectWithData(responseData,
options: []) as! NSDictionary
} catch {
completionHandler(0, "Error with NSJSONSerialization")
return
}
let numberFromString = Int((post["success"] as? String)!)
completionHandler(success: (numberFromString)!, message: (post["message"] as? String)!)
})
task.resume()
}
Side note, but not related to the fix,
let addIAPUrl:NSString = "http://bla.com/application/addIAP.php?transaction_id=\(transaction_id!)&purchase_date=\(purchase_date)&product_id=\(product_id)&web_order_line_item_id=\(web_order_line_item_id)&userID=\(prefs.valueForKey("userID") as! String!)"
self.apiRequests(addIAPUrl as String, completionHandler: { (success, message) -> Void in
Can easily be replaced with
let addIAPUrl = "http://bla.com/application/addIAP.php?transaction_id=\(transaction_id!)&purchase_date=\(purchase_date)&product_id=\(product_id)&web_order_line_item_id=\(web_order_line_item_id)&userID=\(prefs.valueForKey("userID") as! String!)"
self.apiRequests(addIAPUrl, completionHandler: { (success, message) -> Void in
Because you are converting a String to an NSString then back to a String