How to send notification when api changes its data? I am getting data from url. When the data at this address changes, the user must receive it. I tried to use "UNTimeIntervalNotificationTrigger(timeInterval: 60, repeats: true)" but it sends the notification without change.
func apiNotification(urlString:String){
if let url = URL(string: urlString) {
if let data = try? Data(contentsOf: url) {
parse(jsonData: data)
}
}
}
private func notificationSend(id:String, title: String, introtext: String){
let content = UNMutableNotificationContent()
content.subtitle = title
content.body = introtext
content.sound = UNNotificationSound.default
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 30, repeats: false)
let uid = UUID.init().uuidString
let request = UNNotificationRequest(identifier: uid, content: content, trigger: trigger)
UNUserNotificationCenter.current().add(request) { (error) in
if let error = error {
print("Error \(error.localizedDescription)")
} else{
print("Notification Register Success")
print("===================================")
}
}
}
private func parse(jsonData: Data) {
do {
let decodedData = try JSONDecoder().decode(ApiData.self, from: jsonData)
notificationSend(id:decodedData.id, title: decodedData.title, introtext: decodedData.introtext)
} catch {
print("decode error")
}
}
API JSON:
{
"id": "176",
"title": "Notification title 1",
"introtext": "Notification introtext 1",
}
Changed JSON API:
{
"id": "177",
"title": "Notification title 2",
"introtext": "Notification introtext 2",
}
Please help me
Your API must send an event when the data is changing. You must use the framework that Apple is providing; you need to enable this in the app settings, under capabilities, then push notifications. And your API needs to use a key registered in the Apple Developer Platform.
More details in this tutorial:
https://www.raywenderlich.com/11395893-push-notifications-tutorial-getting-started
Related
I have to call method that retrieve data from server every hour in background. For instance when click home button disappear app. Then run code every hour and send local notifications. My code is
#objc func getCustomerOrders() {
let headers = [
"accept": "application/json",
"Username": "info#bse.com.cy",
"Password": "1234"
]
Alamofire.request("https://leathermaster.bse.com.cy/api/getcompletedorders", method:.get, encoding: JSONEncoding.default, headers: headers) .responseJSON { response in
switch (response.result) {
case .success( _):
do {
self.orders = try JSONDecoder().decode([OrderComplete].self, from: response.data!)
if self.orders.count > 0 {
for i in 0...self.orders.count-1 {
if self.orders[i].PhoneNumber == self.preferences.string(forKey: "phone") {
print("TransactionStatus \(self.orders[i].TransactionStatus)")
let date = self.orders[i].TransactionDate.date.replacingOccurrences(of: ".000000", with: "")
let center = UNUserNotificationCenter.current()
let content = UNMutableNotificationContent()
content.title = "\(self.preferences.string(forKey: "firstname")!) \(self.preferences.string(forKey: "surname")!) your order with \(self.orders[i].TransactionCode) transaction code is ready for pickup"
content.body = "\(self.orders[i].TransactionStatus) Date Time: \(date)"
content.sound = .default
content.userInfo = ["value": "Data with local notification"]
let fireDate = Calendar.current.dateComponents([.day, .month, .year, .hour, .minute, .second], from: Date().addingTimeInterval(20))
//let trigger = UNCalendarNotificationTrigger(dateMatching: fireDate, repeats: true)
let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 3600, repeats: false)
let request = UNNotificationRequest(identifier: "reminder", content: content, trigger: trigger)
center.add(request) { (error) in
if error != nil {
print("Error = \(error?.localizedDescription ?? "error local notification")")
}
}
}
}
}
} catch let error as NSError {
print("Failed to load: \(error)")
}
case .failure(let error):
print("Request error: \(error.localizedDescription)")
}
}
}
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self, selector: #selector(getCustomerOrders), name: UIApplication.willResignActiveNotification, object: nil)
I try to run code when app is closed and send periodically local notifications in swift iOS. I hope to give me a solution an expertise programmer.
Here is the problem when I click to the button it gives me response from server saying Mobile number is not valid I think because phone number must include + which refers to the key of the country and I don't know if my body in the code support (+) or not
Note the body from the server, it should be like that
"username": "asik-tech1s22ss22",
"accountType": "Individual",
"name": "asik-tech",
"mobile": "+201227823311",
"password": "asik2020",
"password_confirmation": "asik2020",
"gender": "Female",
"email": "admin122#wasik-tessch.codm2",
"country": "Egypt"
My Code :
#IBAction func formNextButton_clicked(_ sender: UIButton) {
let url = URL(string: "http://18.224.184.27:7000/api/v1/users/root-create")!
let body = "email=\(companyEmailField.text!.lowercased())&name=\(companyNameField.text!.lowercased())&username=\(userNameField.text!.lowercased())&password=\(passwordField.text!)&password_confirmation=\(confirmPasswordField.text!)&country=\(countryField.text!)&mobile=\(phoneNumberField.text!)&gender=\(genderField.text!)&accountType=\(sender.tag)"
print(body)
var request = URLRequest(url: url)
request.httpBody = body.data(using: .utf8)
request.httpMethod = "POST"
// STEP 2. Execute created above request
URLSession.shared.dataTask(with: request) { (data, response, error) in
// access helper class
let helper = Helper()
// error
if error != nil {
helper.showAlert(title: "Server Error", message: error!.localizedDescription, in: self)
return
}
// fetch JSON if no error
do {
// save mode of casting data
guard let data = data else {
helper.showAlert(title: "Data Error", message: error!.localizedDescription, in: self)
return
}
// fetching all JSON received from the server
let json = try JSONSerialization.jsonObject(with: data, options: .allowFragments) as? NSDictionary
print(json as Any)
// error while fetching JSON
} catch {
helper.showAlert(title: "JSON Error", message: error.localizedDescription, in: self)
}
}.resume()
}
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'm pretty new to iOS Programming, and I'm stuck on this little portion here.
I'm basically trying to use Stripe in my iOS application. The user inputs an amount they wish to pay, press NEXT and they input their card details. All of this is going well so far - I can get the AddCardViewController to show up, and input their card details.
The code makes it all the way to verifying the card token and accepting the payment; which is wonderful, but then the problem comes that the AddCardViewController doesn't go away afterward.
When I was trying it out on a demo, it worked perfectly fine, but on my app it doesn't work at all! The app just gets stuck there on the AddCardViewController. Hitting cancel does nothing, there's no display message, and submitting the card details again just double bills the user.
What am I doing wrong?
For further info, this is my code:
StripeClient.shared.sendPostRequest(with: token, amount: amount, description: description) { result in
switch result {
// 1
case .success:
completion(nil)
let alertController = UIAlertController(title: "Congrats",
message: "Your payment was successful!",
preferredStyle: .alert)
let alertAction = UIAlertAction(title: "OK", style: .default, handler: { _ in
self.navigationController?.popViewController(animated: true)
})
alertController.addAction(alertAction)
self.present(alertController, animated: true)
self.view.makeToast("Your payment was successful! We will be sending you a receipt to your email shortly.")
// 2
case .failure(let error):
completion(error)
}
}
I made my custom post request because I couldn't get the one offered by the demo to work, but I can say with 100% certainty that the code enters case .success successfully - I tested this already. But just in case, this is my .sendPostRequest method:
func sendPostRequest(with token: STPToken, amount: Double, description: String, completion: #escaping (Result) -> Void) {
//declare parameter as a dictionary which contains string as key and value combination. considering inputs are valid
let params: [String: Any] = [
"stripeToken": token.tokenId,
"amount": amount,
"currency": Constants.defaultCurrency,
"description": description
]
//create the url with URL
let url = URL(string: "<this-is-my-url.com>")! //change the url
//create the session object
let session = URLSession.shared
//now create the URLRequest object using the url object
var request = URLRequest(url: url)
request.httpMethod = "POST" //set http method as POST
do {
request.httpBody = try JSONSerialization.data(withJSONObject: params, options: .prettyPrinted) // pass dictionary to nsdata object and set it as request body
} catch let error {
print(error.localizedDescription)
}
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
//create dataTask using the session object to send data to the server
let task = session.dataTask(with: request as URLRequest, completionHandler: { data, response, error in
guard error == nil else {
return
}
guard let data = data else {
return
}
do {
//create json object from data
//print(data)
if let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] {
//print(json)
// handle json...
//print(json["response"]!)
if let responseString = try json["response"] as? String {
if responseString == "SUCCESS" {
completion(Result.success)
} else {
completion(Result.failure(IntParsingError.overflow))
}
}
}
} catch let error {
print(error.localizedDescription)
}
})
task.resume()
}
I would really, really, REALLY appreciate the help!
1- You need
DispatchQueue.main.async {
completion(Result.success)
}
As session.dataTask(with: runs in background thread
2- For this to run
self.navigationController?.popViewController(animated: true)
verify the vc is embedded inside a navigation and you presented it with push not present func of the previous vc
Edit:
When you present do
self.dismiss(animated: true, completion: nil)
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()
}