UPDATE
After some comments I changed the resolveVisita method to use URLSession in the background, but it looks like the background session will only start when the app is in the foreground, here is the updated method that runs when a notification action is tapped:
static func resolverVisita(idMensagem: String, resposta: String, liberar: Bool, view: UIViewController?) {
//let configuration = URLSessionConfiguration.background(withIdentifier: "url_session_resolve_visita")
//var backgroundSession = URLSession(configuration: configuration)
// Set up the URL request
let todoEndpoint: String = URL_RESPONDE_VISITA
guard let url = URL(string: todoEndpoint) else {
print("Error: cannot create URL")
return
}
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST"
let postString = "userUUID=\(SessionManager.getUsrUUID())&devUUID=\(devUUID)&msgID=\(idMensagem)&tarefa=\(liberar ? "L" : "B")&resposta=\(resposta)"
urlRequest.httpBody = postString.data(using: .utf8)
let config = URLSessionConfiguration.background(withIdentifier: "\(Bundle.main.bundleIdentifier!).responde_visita")
let session = URLSession(configuration: config)
/*
let task = URLSession.shared.dataTask(with: urlRequest) { data, response, error in
guard let data = data, error == nil else { // check for fundamental networking error
print("error=\(error)")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 { // check for http errors
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(response)")
}
let responseString = String(data: data, encoding: .utf8)
print("responseString = \(responseString)")
}*/
let task = session.dataTask(with: urlRequest)
task.resume()
}
The commented out code seem to work sometimes, but it looks like it is not the right way of doing it.
UPDATE END
So i have an application that receives notification and it has 2 actions, and upon user response the app should send a response to the server, preferable on the background.
I am facing an issue where whenever an action is tapped, sometimes the userNotificationCenter method is not called until the app is opened, and sometimes it does run, but the Alamofire server call does not get processed, and then if i open the app some errors are shown in the console regarding the Alamofire call, however if i tap the action then open the app quick enough it works as expected.
I already enabled Background Fetch and Remote notifications under the app capabilities on Xcode.
This is how I am creating the actions for the notification:
let liberar = UNTextInputNotificationAction(identifier:"liberar", title:"Liberar",options:[.authenticationRequired],
textInputButtonTitle: "Liberar",
textInputPlaceholder: "Resposta")
let bloquear = UNTextInputNotificationAction(identifier: "bloquear", title: "Bloquear", options: [.destructive],
textInputButtonTitle: "Bloquear",
textInputPlaceholder: "Resposta")
Here is my UNUserNotificationCenterDelegate protocol implementation:
extension AppDelegate : UNUserNotificationCenterDelegate {
// Receive displayed notifications for iOS 10 devices.
func userNotificationCenter(_ center: UNUserNotificationCenter,
willPresent notification: UNNotification,
withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void) {
let userInfo = notification.request.content.userInfo
// With swizzling disabled you must let Messaging know about the message, for Analytics
// Messaging.messaging().appDidReceiveMessage(userInfo)
// Print message ID.
if let messageID = userInfo[gcmMessageIDKey] {
print("Message ID: \(messageID)")
}
// Print full message.
print(userInfo)
// Change this to your preferred presentation option
completionHandler([.alert, .badge, .sound])
}
func userNotificationCenter(_ center: UNUserNotificationCenter,
didReceive response: UNNotificationResponse,
withCompletionHandler completionHandler: #escaping () -> Void) {
let userInfo = response.notification.request.content.userInfo
let acao = response.actionIdentifier
//let request = response.notification.request
// Print message ID.
if let messageID = userInfo[gcmMessageIDKey] {
print("Message ID: \(messageID)")
}
print(acao)
if acao == "liberar" {
// Print full message.
let textResponse = response as! UNTextInputNotificationResponse
print("Liberando Visita")
AppConfig.resolverVisita(idMensagem: (userInfo["msgid"] as? String)!, resposta: textResponse.userText, liberar: true, view: nil)
}
else if acao == "bloquear" {
let textResponse = response as! UNTextInputNotificationResponse
print("Bloqueando Visita")
AppConfig.resolverVisita(idMensagem: (userInfo["msgid"] as? String)!, resposta: textResponse.userText, liberar: false, view: nil)
//let newContent = request.content.mutableCopy() as! UNMutableNotificationContent
//print(textResponse.userText)
}
completionHandler()
}}
And here is the resolveVisita method, it basically just gathers some data and send it via a POST request using Alamofire.
static func resolverVisita(idMensagem: String, resposta: String, liberar: Bool, view: UIViewController?) {
if view != nil {
showLoading(mensagem: NSLocalizedString("Processando...", comment: ""), view: view!)
}
//Parameters to be sent
let parameters: Parameters=[
"userUUID":SessionManager.getUsrUUID(),
"devUUID":devUUID,
"msgID": idMensagem,
"resposta": resposta,
"tarefa": liberar ? "L" : "B"
];
//Sending http post request
Alamofire.request(URL_RESPONDE_VISITA, method: .post, parameters: parameters).responseJSON{
response in
//printing response
print(response)
//getting the json value from the server
if let result = response.result.value {
//converting it as NSDictionary
let jsonData = result as! NSDictionary
var mensagem : String = "Solicitacao resolvida com sucesso"
//displaying the message in label
if((jsonData["error"] as! Bool)){
mensagem = jsonData["error_msg"] as! String
if view == nil {
Notificacoes.notificaErroSolicitavao(msgId: idMensagem, errMsg: mensagem)
}
}
if view != nil {
view?.dismiss(animated: false){
showAlert(mensagem: NSLocalizedString(mensagem, comment: ""), view: view!, okMsg: NSLocalizedString("Voltar", comment: ""), segue: "voltarParaLogin")
}
}
else{
print(mensagem)
}
}
}
}
If I just add the .foreground option to the actions making the app open once it is selected, the problem is solved however I really think it would not be necessary for the app to be open for this task.
So after some time I've decided to come back to this issue with a fresh mind, and I discovered what I was doing wrong, I Was trying to create my Instance of the URLSession in the background from the lock screen, and also creating a new instance of the "same Session", to resolve this, all I had to do was create a static instance of the URLSession and use it afterwards whenever I need it.
I created it this way.
static let resolveVisitaSession = URLSession(configuration: URLSessionConfiguration.background(withIdentifier: "br.com.freaccess.resolveVisitaRequest"))
And this is the method that gets called upon the selection on a UNNotificationAction I use my URLSession in there.
static func resolverVisita(idMensagem: String, resposta: String, liberar: Bool) {
print("Entered resolveVisita method")
// Set up the URL request
let todoEndpoint: String = URL_RESPONDE_VISITA
guard let url = URL(string: todoEndpoint) else {
print("Error: cannot create URL")
return
}
var urlRequest = URLRequest(url: url)
urlRequest.httpMethod = "POST"
let postString = "userUUID=\(SessionManager.getUsrUUID())&devUUID=\(devUUID)&msgID=\(idMensagem)&tarefa=\(liberar ? "L" : "B")&resposta=\(resposta)&skipSocket=true"
urlRequest.httpBody = postString.data(using: .utf8)
resolveVisitaSession.dataTask(with: urlRequest).resume()
}
Related
In my iOS App i'm able to download data from a database, but actually all the operations are made in background and the main thread is still active, even the GUI. I also tried to make a 'sleep' with
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3)) { ... }
With this delay everthing works fine, but it's not a good solution. How can i change my code to do this in the main thread? Possibly with loadingIndicator.
This is my code (checking if username exists):
func CheckIfUsernameExists(username : String, passwordFromDb : inout String, errorMsg : inout String)
{
//declare parameter as a dictionary which contains string as key and value combination. considering inputs are valid
var _errorMsg = ""
var _psw = ""
var parameters : [String : Any]?
parameters = ["username": username,
"action": "login"]
print(parameters!)
let session = URLSession.shared
let url = "http://www.thetestiosapp.com/LoginFunctions.php"
let request = NSMutableURLRequest()
request.url = URL(string: url)!
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField:"Accept")
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField:"Content-Type")
do{
request.httpBody = try JSONSerialization.data(withJSONObject: parameters!, options: .sortedKeys)
let task = session.dataTask(with: request as URLRequest, completionHandler: {(data, response, error) in
if let response = response {
let nsHTTPResponse = response as! HTTPURLResponse
let statusCode = nsHTTPResponse.statusCode
print ("status code = \(statusCode)")
}
if let error = error {
print ("\(error)")
}
if let data = data {
do{
_psw = self.parseJSON_CheckIfUsernameExists(data, errorMsg: &_errorMsg)
}
}
})
task.resume()
}catch _ {
print ("Oops something happened buddy")
errorMsg = "Usarname non recuperato (1)"
}
passwordFromDb = _psw
errorMsg = _errorMsg
}
You’re attempting to update passwordFromDb and errorMsg at the end of this method. But this is an asynchronous method and and those local variables _psw and _errorMsg are set inside the closure. Rather than trying to defer the checking of those variables some arbitrary three seconds in the future, move whatever “post request” processing you need inside that closure. E.g.
func CheckIfUsernameExists(username : String, passwordFromDb : inout String, errorMsg : inout String) {
//declare parameter as a dictionary which contains string as key and value combination. considering inputs are valid
let parameters = ...
let session = URLSession.shared
var request = URLRequest()
...
do {
request.httpBody = ...
let task = session.dataTask(with: request) { data, response, error in
if let httpResponse = response as? HTTPURLResponse,
let statusCode = httpResponse.statusCode {
print ("status code = \(statusCode)")
}
guard let data = data else {
print (error ?? "Unknown error")
return
}
let password = self.parseJSON_CheckIfUsernameExists(data, errorMsg: &_errorMsg)
DispatchQueue.main.async {
// USE YOUR PASSWORD AND ERROR MESSAGE HERE, E.G.:
self.passwordFromDb = password
self.errorMsg = _errorMsg
// INITIATE WHATEVER UI UPDATE YOU WANT HERE
}
}
task.resume()
} catch _ {
print ("Oops something happened buddy")
errorMsg = "Usarname non recuperato (1)"
}
}
I am getting crash
it says *** Terminating app due to uncaught exception 'NSGenericException', reason: 'Completion handler blocks are not supported in background sessions. Use a delegate instead.'
var Boundary = "\(boundary.generateBoundaryString())_boundary"
private lazy var session: URLSession = {
let config = URLSessionConfiguration.background(withIdentifier: "MyUniqeId")
config.isDiscretionary = true
config.sessionSendsLaunchEvents = true
return URLSession(configuration: config, delegate: self, delegateQueue: nil)
}()
func webServiceForUploadImages(urlStr:String,params:[String:String],fileUrl:String,imageData:Data,success :#escaping (AppMedia) -> Void ,failure:#escaping (NSError) -> Void) -> Void{
let url = Constant.BASE_URL + urlStr
print(url)
if(reachAbility.connection != .none){
var request = URLRequest(url: URL(string: url)!)
request.httpMethod = "POST"
request.allHTTPHeaderFields = Header.headers()
request.setValue("multipart/form-data; boundary=\(Boundary)", forHTTPHeaderField: "Content-Type")
let data = try! createBody(with: params, filePathKey: "file", paths: [fileUrl], boundary: "\(Boundary)", imageData: imageData)
session.uploadTask(with: request, from: data) { (data, response, err) in
if response != nil{
guard let response = response as? HTTPURLResponse else {return}
handleError.shared.HandleReponseTokenExpireError(dataResponse: response, success: { (response) in
})
if(err != nil){
print("\(err!.localizedDescription)")
}
guard let responseData = data else {
print("no response data")
return
}
if let responseString = String(data: responseData, encoding: .utf8) {
DispatchQueue.main.async {
let dict = Utility.jsonToDict(str: responseString)
let mediaDict = AppMedia(fromDictionary: dict as! [String : Any])
Constant.KAppDelegate.hideProgressHUD()
success(mediaDict)
}
// print("uploaded to: \(responseString)")
}
}else{
DispatchQueue.main.async {
failure(err! as NSError)
Constant.KAppDelegate.hideProgressHUD()
Constant.KAppDelegate.showErrorMessage(title: "Error", message: Constant.ServerError, msgType: .error)
}
}
}.resume()
}else{
self.showErrorMsg(str: Constant.ConnectivityError)
}
}
let config = URLSessionConfiguration.background(withIdentifier: "MyUniqeId")
using this gives me crash
To upload using background URLSessionConfiguration there are a few special considerations:
Cannot use completion handlers (because the app might not be running when you finish the upload). You must use delegate-base methods, e.g. uploadTask(with:fromFile:).
For example:
func startUpload(for request: URLRequest, from data: Data) throws -> URLSessionUploadTask {
let fileURL = URL(fileURLWithPath: NSTemporaryDirectory())
.appendingPathComponent(UUID().uuidString)
try data.write(to: fileURL)
let task = session.uploadTask(with: request, fromFile: fileURL)
task.resume()
return task
}
That obviously assumes that you’ve specified your delegate and implemented the appropriate delegate methods:
extension BackgroundSession: URLSessionDelegate {
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
DispatchQueue.main.async {
self.savedCompletionHandler?()
self.savedCompletionHandler = nil
}
}
}
extension BackgroundSession: URLSessionTaskDelegate {
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let error = error {
print(error)
return
}
print("success")
}
}
Note, we cannot use uploadTask(with:from:) because that’s using Data for that second parameter, which is not allowed for background sessions. Instead one must save the body of the request into a file and then use uploadTask(with:fromFile:).
Remember to handle the scenario where the upload finishes when your app is not running. Namely, the app delegate’s handleEventsForBackgroundURLSession must capture the completion handler. For example, I’ll have a property in my BackgroundSession to save the completion handler:
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: #escaping () -> Void) {
BackgroundSession.shared.savedCompletionHandler = completionHandler
}
And then you want to implement urlSessionDidFinishEvents(forBackgroundURLSession:) and call the saved completion handler:
extension BackgroundSession: URLSessionDelegate {
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
DispatchQueue.main.async {
self.savedCompletionHandler?()
self.savedCompletionHandler = nil
}
}
}
By the way, Downloading Files in the Background discusses many of these considerations (e.g. delegate API rather than closure based API, app delegate issues, etc.). It even discusses the requirements that upload tasks are file-based, too.
Anyway, here is a sample BackgroundSession manager:
import os.log
// Note, below I will use `os_log` to log messages because when testing background URLSession
// you do not want to be attached to a debugger (because doing so changes the lifecycle of an
// app). So, I will use `os_log` rather than `print` debugging statements because I can then
// see these logging statements in my macOS `Console` without using Xcode at all. I'll log these
// messages using this `OSLog` so that I can easily filter the macOS `Console` for just these
// logging statements.
private let log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: #file)
class BackgroundSession: NSObject {
var savedCompletionHandler: (() -> Void)?
static var shared = BackgroundSession()
private var session: URLSession!
private override init() {
super.init()
let identifier = Bundle.main.bundleIdentifier! + ".backgroundSession"
let configuration = URLSessionConfiguration.background(withIdentifier: identifier)
session = URLSession(configuration: configuration, delegate: self, delegateQueue: nil)
}
}
extension BackgroundSession {
#discardableResult
func startUpload(for request: URLRequest, from data: Data) throws -> URLSessionUploadTask {
os_log("%{public}#: start", log: log, type: .debug, #function)
let fileURL = URL(fileURLWithPath: NSTemporaryDirectory())
.appendingPathComponent(UUID().uuidString)
try data.write(to: fileURL)
let task = session.uploadTask(with: request, fromFile: fileURL)
task.resume()
return task
}
}
extension BackgroundSession: URLSessionDelegate {
func urlSessionDidFinishEvents(forBackgroundURLSession session: URLSession) {
os_log(#function, log: log, type: .debug)
DispatchQueue.main.async {
self.savedCompletionHandler?()
self.savedCompletionHandler = nil
}
}
}
extension BackgroundSession: URLSessionTaskDelegate {
func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
if let error = error {
os_log("%{public}#: %{public}#", log: log, type: .error, #function, error.localizedDescription)
return
}
os_log("%{public}#: SUCCESS", log: log, type: .debug, #function)
}
}
extension BackgroundSession: URLSessionDataDelegate {
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didBecome downloadTask: URLSessionDownloadTask) {
os_log(#function, log: log, type: .debug)
}
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
os_log("%{public}#: received %d", log: log, type: .debug, #function, data.count)
}
}
And, of course, my app delegate:
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
...
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: #escaping () -> Void) {
BackgroundSession.shared.savedCompletionHandler = completionHandler
}
}
Needless to say, that’s a lot to worry about with uploads in conjunction with background URLSession. If you’re uploading huge assets (e.g. videos) over slow connections, perhaps you need that. But the other (simpler) alternative is to use a default URLSession configuration, and just tell the OS that even if the user leaves your app, request a little more time to finish the upload. Just use standard completion handler pattern with default URLSession, and marry that with the techniques outlined in Extending Your App's Background Execution Time. Now, that only buys you 30 seconds or so (it used to be 3 minutes in older iOS versions), but often that’s all we need. But if you think it may take more than 30 seconds to finish the uploads, then you’ll need background URLSessionConfiguration.
Change your
let config = URLSessionConfiguration.background(withIdentifier: "MyUniqeId")
To
let config = URLSession(configuration: URLSessionConfiguration.default)
complete code will like below
let config = URLSession(configuration: URLSessionConfiguration.default)
var Boundary = "\(boundary.generateBoundaryString())_boundary"
private lazy var session: URLSession = {
let config = URLSession(configuration: URLSessionConfiguration.default)
config.isDiscretionary = true
config.sessionSendsLaunchEvents = true
return URLSession(configuration: config, delegate: self, delegateQueue: nil)
}()
func webServiceForUploadImages(urlStr:String,params:[String:String],fileUrl:String,imageData:Data,success :#escaping (AppMedia) -> Void ,failure:#escaping (NSError) -> Void) -> Void{
let url = Constant.BASE_URL + urlStr
print(url)
if(reachAbility.connection != .none){
var request = URLRequest(url: URL(string: url)!)
request.httpMethod = "POST"
request.allHTTPHeaderFields = Header.headers()
request.setValue("multipart/form-data; boundary=\(Boundary)", forHTTPHeaderField: "Content-Type")
let data = try! createBody(with: params, filePathKey: "file", paths: [fileUrl], boundary: "\(Boundary)", imageData: imageData)
session.uploadTask(with: request, from: data) { (data, response, err) in
if response != nil{
guard let response = response as? HTTPURLResponse else {return}
handleError.shared.HandleReponseTokenExpireError(dataResponse: response, success: { (response) in
})
if(err != nil){
print("\(err!.localizedDescription)")
}
guard let responseData = data else {
print("no response data")
return
}
if let responseString = String(data: responseData, encoding: .utf8) {
DispatchQueue.main.async {
let dict = Utility.jsonToDict(str: responseString)
let mediaDict = AppMedia(fromDictionary: dict as! [String : Any])
Constant.KAppDelegate.hideProgressHUD()
success(mediaDict)
}
// print("uploaded to: \(responseString)")
}
}else{
DispatchQueue.main.async {
failure(err! as NSError)
Constant.KAppDelegate.hideProgressHUD()
Constant.KAppDelegate.showErrorMessage(title: "Error", message: Constant.ServerError, msgType: .error)
}
}
}.resume()
}else{
self.showErrorMsg(str: Constant.ConnectivityError)
}
}
now call it in GCD in you viewController or what ever place you want.
DispatchQueue.global().async {
// call your webServiceForUploadImages with completion blocks
}
NOTE:
Only do if you are in need of completion blocks if completion blocks are not necessary do as suggested in exception use delegation
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)
}
I have this question for Xcode9 Swift 4. I am trying to fetch some data from some api, and I need to use these data to display. However, since the urlSession is highly asynchronous, I cannot get the data at the right time (most of the time the data is nil). Here is the code.
func getUserInfo(){
let data = user!.Data as? [String : Any] ?? nil
if let data = data{
let ID = data["ID"] as? Int ?? nil
if let ID = ID{
let jsonUrlString = "SomeString"
let requestUrl = URL(string: jsonUrlString)
var request = URLRequest(url: requestUrl!)
request.httpMethod = "GET"
request.setValue("SomeKey", forHTTPHeaderField: "AppKey")
request.setValue(md5("Someinfo"), forHTTPHeaderField: "Sign")
dataTask = URLSession.shared.dataTask(with: request){(data, response, err) in
guard let data = data, err == nil else { // check for fundamental networking err
print("error=\(String(describing: err))")
return
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {
// check for http errors
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(String(describing: response))")
}
do{
guard let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [Any] else{return}
var userInfo = userDetail(json: json)
let dataDic = userInfo.dataArray as? [String:Any] ?? nil
userInfo.ID = dataDic?["ID"] as? Int
userInfo.AccountName = dataDic?["AccountName"] as? String
userInfo.Avatar = dataDic?["Avatar"] as? String
} catch let jsonErr{
print(jsonErr)
}
}
dataTask?.resume()
}
}
}
I am storing the data into variable userInfo, which has properties like ID, Account Name, and Avatar. But when I call the function in another method "configNavigationBar", it cannot initialize userInfo for me.
func configNavigationBar(){
getUserInfo()
if dataTask?.state == .completed{
navigationItem.title = userInfo?.AccountName
navigationItem.setHidesBackButton(true, animated: true)
navigationController?.navigationBar.prefersLargeTitles = true
}
}
Can anybody help me with the question! I deeply appreciate any help.
How about changing title after successful http request? In a callback. You can configure everything except title before receiving data.
func configNavigationBar(){
getUserInfo { accountName in
self.navigationItem.title = accountName
}
navigationItem.setHidesBackButton(true, animated: true)
navigationController?.navigationBar.prefersLargeTitles = true
}
func getUserInfo(_ callback: #escaping (String) -> Void) {
...
var userInfo = userDetail(json: json)
let dataDic = userInfo.dataArray as? [String:Any] ?? nil
userInfo.ID = dataDic?["ID"] as? Int
userInfo.AccountName = dataDic?["AccountName"] as? String
userInfo.Avatar = dataDic?["Avatar"] as? String
// here's the insertion
callback(userInfo.AccountName)
// end of insertion
....
}
You might also need to wrap ui update into main thread if http request is using background thread.
getUserInfo { accountName in
DispatchQueue.main.async {
self.navigationItem.title = accountName
}
}
You simply need to wait until your data has been downloaded.
While download is busy you should decide to show temporary state on your navigation bar.
Then, when download is finished (or fails), you update the navigation bar again.
I am new to web development and Swift
I created a web api based on ASP.NET and I connected my ios app so I can do GET, POST, PUT, DELETE.
When I send GET request with specific ID number
I get output in Xcode as following:
Data:
Optional("{\"Id\":1,\"UserName\":\"codeinflash\",\"UserPassword\":\"Wldnrodxxxx\",\"UserEmail\":\"codeinflash#gmail.com\",\"Rebate\":0.00,\"MemCom\":123.44}")
Here is function in Swift:
//GET /api/account/{id}
#IBAction func GetAccount(_ sender: Any) {
let _accountIdNumber = AccountIdNumber.text
if (_accountIdNumber!.isEmpty){
createAlert(title: "Warning", message: "Account ID # is required")
return
}
let restEndPoinst: String = "http://tresmorewebapi2.azurewebsites.net/api/account/" + _accountIdNumber!;
guard let url = URL(string: restEndPoinst) else {
print("Error creating URL")
return
}
var urlRequest = URLRequest(url: url)
urlRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
// api key need urlRequest.setValue(<#T##value: String?##String?#>, forHTTPHeaderField: "APIKey")
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
var userEmail = ""
var rebate = ""
var memcom = ""
let task = session.dataTask(with: urlRequest, completionHandler:
{
(data, response, error) in
print("Error:")
print(error)
print("response:")
print(response)
print("Data:")
print(String(data: data!, encoding: String.Encoding.utf8))
//////////////I think I need logic here to filter the data
userEmail = data.substring of find emailaddress
rebate = data.substring find users' rebate
memcom = same logic
then show alert window with his info and I will show his info on next page which is a Dashboard page view
})
task.resume()
}
Honestly I am not sure the Data is JSON data but the output in Xcode is in string.
My purpose is get uer's data and store in local variables that passes them to next view(Dashboard) in ios Swift.
Thank you!
Added
Here is the task getting data from web api in login fuction swift
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
var userEmail = ""
var rebate = ""
var memcom = ""
let task = session.dataTask(with: urlRequest, completionHandler:
{
(data: Data?, response: URLResponse?, error: Error?) in
print("Error:")
print(error)
print("response:")
print(response)
print("Data:")
print(String(data: data!, encoding: String.Encoding.utf8))
let json = try? JSONSerialization.jsonObject(with: data!) as! [String: AnyObject] ?? [:];
userEmail = json?["UserEmail"] as? String ?? ""
createAlert(title: "Got User Eamil Address!", message: userEmail)
})
task.resume()
But I get nothing in my alert view. The alert view working fine I tested.
Here is my createAlert fuction in Swift
func createAlert(title:String, message:String){
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: { (anction) in
alert.dismiss(animated: true, completion: nil)}))
self.present(alert, animated: true, completion: nil)
}
You have answer in comments already, but this is less "swifty" way, more readable for someone new to Swift (pay attention and try to understand what is going on and how you can make this more compact):
do {
guard let unwrappedData = data else{
//data is nil - handle case here
return //exit scope
}
guard let dictionary = try JSONSerialization.jsonObject(with: unwrappedData) as? [String:AnyObject] else {
//you cannot cast this json object to dictionary - handle case here
return //exit scope
}
userEmail = dictionary["UserEmail"]
DispatchQueue.main.async { [weak self] in
self?.createAlert(title: "Got User Eamil Address!", message: userEmail)
}
} catch _ {
//handle error
}