I'm trying to redirect the output of this API Call decoded in a struct but when I try to use the data to create a dialog it gives me this weird exception. As you can see, the API returns data but only when I create the dialog I see this exception. Can you help me?
Code:
struct rspServerInfo: Codable{
let ok: Bool
let info: String
}
#IBAction func backendDetails(_ sender: Any) {
guard let url = URL(string: "http://\(hostname):\(port)/STOInfo/ServerInfo")else{
return
}
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
let body: [String: AnyHashable] = [
"username": username,
"password": password,
]
request.httpBody = try? JSONSerialization.data(withJSONObject: body, options: .fragmentsAllowed)
let task = URLSession.shared.dataTask(with: request) {data, _, error in
guard let data=data, error == nil else{
return
}
do{
let response = try JSONDecoder().decode(rspServerInfo.self, from: data)
print("SUCCESS: \(response)")
let dialogMessage = UIAlertController(title: "Backend details", message: response.info, preferredStyle: .alert)
let ok = UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in
print("Ok button tapped")
})
dialogMessage.addAction(ok)
self.present(dialogMessage, animated: true, completion: nil)
}
catch{
print(error)
let dialogMessage = UIAlertController(title: "Backend details", message: "Error retreiving.", preferredStyle: .alert)
let ok = UIAlertAction(title: "OK", style: .default, handler: { (action) -> Void in
print("Ok button tapped")
})
dialogMessage.addAction(ok)
self.present(dialogMessage, animated: true, completion: nil)
}
}
task.resume()
}
The error message give a big clue:
Terminating app due to uncaught exception
'NSInternalInconsistencyException', reason: 'Modifications to the
layout engine must not be performed from a background thread after it
has been accessed from the main thread.' terminating with uncaught
exception of type NSException
The URLRequest run asynchronously on a background thread, including its completion handlers. You (generally) can't do UI work outside of the main thread. To display the results you need to push the operation back onto the main thread:
DispatchQueue.main.async {
// do UI work
}
Related
I try to call my API, but I get an error:
Modifications to the layout engine must not be performed from a background thread after it has been accessed from the main thread.
I understand that I need to call DispatchQueue, but I don't understand where I need use it.
My code:
let currentUser = Auth.auth().currentUser
currentUser?.getIDTokenForcingRefresh(true, completion: { (idToken, error) in
if let err = error {
self.unknownError(error: err)
} else {
var request = URLRequest(url: URL(string: "https://phss.ru/api/booking/cancel")!)
request.httpMethod = "POST"
let cancelBooking: [String: Any] = ["booking_id": self.documentIDs]
let jsonData = try! JSONSerialization.data(withJSONObject: cancelBooking, options: [])
request.httpBody = jsonData
request.addValue("Bearer \(idToken!)", forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in
if let err = error {
self.unknownError(error: err)
} else {
let alertController = UIAlertController(title: NSLocalizedString("Cancel successful", comment: "Cancel successful"), message: "", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: NSLocalizableOk, style: .cancel, handler: nil))
self.present(alertController, animated: true, completion: nil)
}
})
task.resume()
}
})
You need to present your UIAlertController on the main thread because the completion callback of URLSession.shared.dataTask(with:completionHandler:) runs on a background thread.
DispatchQueue.main.async {
let alertController = UIAlertController(title: NSLocalizedString("Cancel successful", comment: "Cancel successful"), message: "", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: NSLocalizableOk, style: .cancel, handler: nil))
self.present(alertController, animated: true, completion: nil)
}
I started learning Swift and are not familiar with synchronous and asynchronous operations with swift code.
I want to be in my login view (viewcontroller), when the user enters the ID, PASSWORD, and then requests the service from the API, the API will compare the database data is correct, if the correct return json data => true, the error returns json data => false
I don't know how to make my login() execute after getting the API response.
I have researched a lot of information about this, including NSOperationQueue ... etc.
But the final result of the implementation failed, please help me with experienced people, thank you!
var jsonmessage: Bool? = nil
when onclickLogin will go next page using segue
#IBAction func onclickLogin(_ sender: Any) {
Postusercheck()
login()
}
request api
func Postusercheck(){
let parameters = ["ID":txtcount.text,"Password":txtpaswoerd.text] //post request with id,password
print(parameters)
let url = URL(string: "http://" + apiip + "/api/user")! //change the url
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: parameters, options: .prettyPrinted) // pass
} catch let error {
print(error.localizedDescription)
}
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
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 {
if let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] {
print(json)
self.jsonmessage = json["message"] as? Bool
print("Title: \(String(describing: self.abc)))")
}
} catch let error {
print(error.localizedDescription)
}
})
task.resume()
}
func login(){
if (self.jsonmessage == true){
}
else if txtcount.text == "" || txtpaswoerd.text == "" {
let alert = UIAlertController(title: "Don't empty the field", message: "Please enter again", preferredStyle: .alert)
let ok = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(ok)
present(alert, animated: true, completion: nil)
}
else
{
let alert = UIAlertController(title: "ID OR PASSWORD ERROR", message: "Please enter again", preferredStyle: .alert)
let ok = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(ok)
present(alert, animated: true, completion: nil)
}
}
========================updated========code======
#IBAction func onclickLogin(_ sender: Any) {
Postusercheck()
}
func Postusercheck(){
let parameters = ["ID":txtcount.text,"Password":txtpaswoerd.text] //post request with id,password
print(parameters)
let url = URL(string: "http://" + apiip + "/api/user")! //change the url
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: parameters, options: .prettyPrinted) // pass
} catch let error {
print(error.localizedDescription)
}
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("application/json", forHTTPHeaderField: "Accept")
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 {
if let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] {
print(json)
self.jsonmessage = json["message"] as? Bool
self.login()
}
} catch let error {
print(error.localizedDescription)
}
})
task.resume()
}
func login(){
if (self.jsonmessage == true){
//Navigate to the another view controller
let mainStoryboard = UIStoryboard.init(name: "Main", bundle: nil)
let anotherViewController = mainStoryboard.instantiateViewController(withIdentifier: "AnotherViewController")
self.navigationController?.pushViewController(anotherViewController, animated: true)
}
else if txtcount.text == "" || txtpaswoerd.text == "" {
let alert = UIAlertController(title: "Don't empty the field", message: "Please enter again", preferredStyle: .alert)
let ok = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(ok)
present(alert, animated: true, completion: nil)
}
else
{
let alert = UIAlertController(title: "ID OR PASSWORD ERROR", message: "Please enter again", preferredStyle: .alert)
let ok = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(ok)
present(alert, animated: true, completion: nil)
}
}
When I try the new code.
I have canceled the original segue connection. I set the Storboard ID to AnotherViewController on the page I want, but when the account password is successfully verified, he does not go to the next page.
He just stopped at the original page (after the account password verification is successful)
If I enter the wrong account password, it still get an error message. This function is useful.
Make the function call in the completion handler of URLSession like below :
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 {
if let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? [String: Any] {
print(json)
self.jsonmessage = json["message"] as? Bool
self.login()
print("Title: \(String(describing: self.abc)))")
}
} catch let error {
print(error.localizedDescription)
}
})
You have to remove the login call from the button's IBAction cause when you are calling the Postusercheck it is going to make the webservice call and it will not wait for the response as the session.dataTask is asynchronous. So the execution will go back to the IBAction and login() will be called though you have not received the web service response and you don't know if the user name and passwords are correct. So you have to remove login call from the button click action like this :
#IBAction func onclickLogin(_ sender: Any) {
Postusercheck()
}
Change the login function as below :
func login(){
if (self.jsonmessage == true){
//Navigate to the another view controller
let mainStoryboard = UIStoryboard.init(name: "Main", bundle: nil)
let anotherViewController = mainStoryboard.instantiateViewController(withIdentifier: "AnotherViewController")
self.navigationController?.pushViewController(anotherViewController, animated: true)
}
else if txtcount.text == "" || txtpaswoerd.text == "" {
let alert = UIAlertController(title: "Don't empty the field", message: "Please enter again", preferredStyle: .alert)
let ok = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(ok)
present(alert, animated: true, completion: nil)
}
else
{
let alert = UIAlertController(title: "ID OR PASSWORD ERROR", message: "Please enter again", preferredStyle: .alert)
let ok = UIAlertAction(title: "OK", style: .default, handler: nil)
alert.addAction(ok)
present(alert, animated: true, completion: nil)
}
}
Here make sure that you are having a view controller in the storyboard named "Main" with Storyboard Id as "AnotherViewController" otherwise it won't work.
Another option for navigation is through segues in the storyboards. Below is a great tutorial to learn great things about the storyboard.
https://www.raywenderlich.com/464-storyboards-tutorial-for-ios-part-1
Also one more thing is that Postusercheck() is not so good function name. Please refer below guide line for the best practices in the swift's widely used naming conventions :
https://github.com/raywenderlich/swift-style-guide
UPDATED Question:
At first my issue was about assigning a custom ParameterEncoding per ViewController based on the HTTP.Method request, I found a solution and added the string in the Alamofire.request then added import Alamofire to the head of each ViewController which helped calling JSONEncoding and URLEncoding and changed the way of call by adding the header to the request as well to avoid 401 unauthorized error.
func callingHttpRequest(params:Dictionary<String,Any>, apiname:String,cuurentView:UIViewController,method:HTTPMethod, taskCallback: #escaping (Int,
AnyObject?) -> Void) {
let urlString = HOST_NAME + apiname
print("url",urlString)
print("params", params)
Alamofire.request(urlString,method: method,parameters:params).validate().responseJSON { response in
switch response.result {
case .success(let resultData):
taskCallback(1,resultData as AnyObject)
let returnData = String(data: response.data! , encoding: .utf8)
print("returnData" ,returnData!)
print("request URL", response.request!)
break
case .failure(let error):
let returnData = String(data: response.data! , encoding: .utf8)
print("returnData" ,returnData!)
print("request URL", response.request!)
if !Connectivity.isConnectedToInternet(){
NetworkManager.sharedInstance.dismissLoader()
cuurentView.view.isUserInteractionEnabled = true
let AC = UIAlertController(title: "Warning", message: error.localizedDescription, preferredStyle: .alert)
let okBtn = UIAlertAction(title: "Retry", style: .default, handler: {(_ action: UIAlertAction) -> Void in
taskCallback(2, "" as AnyObject)
})
let noBtn = UIAlertAction(title: "Cancel", style: .destructive, handler: {(_ action: UIAlertAction) -> Void in
})
AC.addAction(okBtn)
AC.addAction(noBtn)
cuurentView.present(AC, animated: true, completion: { _ in })
}
else{
let errorCode:Int = error._code;
if errorCode != -999 && errorCode != -1005{
NetworkManager.sharedInstance.dismissLoader()
cuurentView.view.isUserInteractionEnabled = true
let AC = UIAlertController(title: "Warning", message: error.localizedDescription, preferredStyle: .alert)
let okBtn = UIAlertAction(title: "Retry", style: .default, handler: {(_ action: UIAlertAction) -> Void in
taskCallback(2, "" as AnyObject)
})
let noBtn = UIAlertAction(title: "Cancel", style: .destructive, handler: {(_ action: UIAlertAction) -> Void in
})
AC.addAction(okBtn)
AC.addAction(noBtn)
cuurentView.present(AC, animated: true, completion: { _ in })
}else if errorCode == -1005{
NetworkManager.sharedInstance.dismissLoader()
taskCallback(2, "" as AnyObject)
}
}
break;
}
}
Now what's really pissing me off is the response of the request, the app is made with user login so each time the app runs it will check if the user logged in or not. When the HTTP request is sent the response returns with SQL Query added to JSON if the user is logged in as below
Failure returnData string(84) "SELECT * FROM customer where LOWER(user) = 'helloworld' AND status = '1'"
{"success":true,"data":[{,,,,,"}
This is causing a failure response and and error
Warning: JSON could not be serialized because of error: The data couldn't be read because it isn't in the correct format.
This is really insane why the response is returning a SQL Query while it shouldn't. That wasn't happening before I customized the header and encoding of the Alamofire.request.
Any way faced such issue can hep please!
P.S.: The response has 200 OK HTTP Response but with failure Data Response
Im trying to display an alert when a user presses a button to insert text in database, everything works fine but when i get the response and try to display it in alert, it doesn't work instantly, the alert is displayed when i press somewhere on the screen, after pressing the button. I need it to come as soon as the button is pressed , if i print the same response it works fine, but when i use alert it doesn't.
#IBAction func posting(sender: AnyObject) {
let request = NSMutableURLRequest(URL: NSURL(string: "https://example.com/appapi/test2.php")!)
request.HTTPMethod = "POST"
let postString = "a=\(title.text!)"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error inenter code here
if error != nil {
print("error=\(error)")
return
}
let responseString = "\(NSString(data: data!, encoding: NSUTF8StringEncoding)!)"
self.displayR(responseString)
}
task.resume()
}
func displayR(str:String){
var alert = UIAlertController(title: "Description", message: "\(str)" ,preferredStyle: .Alert)
alert.addAction(UIAlertAction(title: "OK", style: .Default, handler: { (action) -> Void in }))
self.presentViewController(alert, animated: true, completion: nil)
}
When you execute data task with request, the code executes in a background thread. Any UI changes (in this case displaying alert) must be done on main thread. To do so put your call to display function in following code:
dispatch_async(dispatch_get_main_queue()) {
//call display function here
}
I am following the "Learning Swift" book from O'reilly, and I have some code that looks like:
func deleteDocumentAtURL(url: NSURL) {
NSLog("Got to top of deleteDocumentAtURL, url: \(url)")
let fileCoordinator = NSFileCoordinator(filePresenter: nil)
fileCoordinator.coordinateWritingItemAtURL(url, options: .ForDeleting, error: nil, byAccessor: { (urlForModifying) -> Void in
NSLog("Here I am")
do {
NSLog("Got inside deleteDocumentBlock")
try NSFileManager.defaultManager().removeItemAtURL(urlForModifying)
// Remove the URL from the list
self.availableFiles = self.availableFiles.filter {
$0 != url
}
// Update the collection
self.collectionView?.reloadData()
} catch let error as NSError {
let alert = UIAlertController(title: "Error deleting", message: error.localizedDescription, preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Done", style: .Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}
})
}
When I run this code, which is triggered by clicking a button in the interface, the application hangs. Also, the log inside the do{} is not firing, which makes me think there's a problem with the whole block? Help appreciated.
Because
let fileCoordinator = NSFileCoordinator(filePresenter: nil)
is local in the function. The variable is already destroyed, if you finished the function.
So, the async block can't be executed.
Put fileCoordinator as an instance variable to the class.
You are not handling error and probably have an error
Documentation for coordinateReadingItemAtURL:
outError: On input, a pointer to a pointer for an error object. If a file presenter encounters an error while preparing for this read operation, that error is returned in this parameter and the block in the reader parameter is not executed. If you cancel this operation before the reader block is executed, this parameter contains an error object on output.
add error handler and see if you get an error
func deleteDocumentAtURL(url: NSURL) {
NSLog("Got to top of deleteDocumentAtURL, url: \(url)")
let fileCoordinator = NSFileCoordinator(filePresenter: nil)
var error: NSError?
fileCoordinator.coordinateWritingItemAtURL(url, options: .ForDeleting, error: &error, byAccessor: { (urlForModifying) -> Void in
NSLog("Here I am")
do {
NSLog("Got inside deleteDocumentBlock")
try NSFileManager.defaultManager().removeItemAtURL(urlForModifying)
// Remove the URL from the list
self.availableFiles = self.availableFiles.filter {
$0 != url
}
// Update the collection
self.collectionView?.reloadData()
} catch let error as NSError {
let alert = UIAlertController(title: "Error deleting", message: error.localizedDescription, preferredStyle: UIAlertControllerStyle.Alert)
alert.addAction(UIAlertAction(title: "Done", style: .Default, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}
})
//Error:
if(error != nil){
print(error)
}
}