Error in background/main thread using API - ios

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)
}

Related

Why calling a dialog with a struct drops this exception?

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
}

How to wait a value from api response for login view using swift

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

Getting login data from mysql in swift login form using json

I am new to swift . I have developed a login form which verifies data from mysql database and for that I have used json and swift code . But there is an issue in my code , after providing the login details when I click on the submit button .It shows the alert view that the credentials are invalid ,even after providing the correct credentials . I have attached the code below . Please if anyone can help me
class ViewController: UIViewController {
#IBOutlet weak var PASSWORD: UITextField!
#IBOutlet weak var USERNAME: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func submitbtn(_ sender: Any) {
let username: NSString = self.USERNAME.text! as NSString
let password: NSString = self.PASSWORD.text! as NSString
if username.isEqual(to: "") || password.isEqual(to: ""){
let myAlert = UIAlertController(title: "Alert", message:"All fields are required to fill in", preferredStyle: UIAlertControllerStyle.alert);
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler:nil)
myAlert.addAction(okAction);
self.present(myAlert, animated: true, completion: nil)
// return
}
else
{
let post:NSString = "UserName\(username)&PassWord\(password)" as NSString
NSLog("PostData : %d", post)
let url = "http://demo.talentclouds.in/API/LoginHandler.asmx/Login?username=admin#penn.in&password=123"
let postData:NSData = post.data(using: String.Encoding.ascii.rawValue)! as NSData
let postLength:NSString = String ( postData.length ) as NSString
let request = NSMutableURLRequest(url: NSURL(string: url)! as URL)
request.httpMethod = "POST"
request.httpBody = postData as Data
request.setValue(postLength as String, forHTTPHeaderField: "Content-Length")
request.setValue("application/x-www-form-urlencoder", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
// let responseError:NSError?
// let response:URLResponse?
let task = URLSession.shared.dataTask(with: request as URLRequest, completionHandler: {data, response, error -> Void in
DispatchQueue.main.async {
if(error != nil)
{
//Display an alert message
let myAlert = UIAlertController(title: "Alert", message: error!.localizedDescription, preferredStyle: UIAlertControllerStyle.alert);
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler:nil)
myAlert.addAction(okAction);
self.present(myAlert, animated: true, completion: nil)
}
//parsing the response
do {
//converting resonse to NSDictionary
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
if let parseJSON = json {
let username = parseJSON["username"] as? String
if(username != nil)
{
UserDefaults.standard.set(parseJSON["username"], forKey: "username")
UserDefaults.standard.set(parseJSON["password"], forKey: "password")
UserDefaults.standard.synchronize()
// let username : NSInteger = json?.value(forKey: username as String)as! NSInteger
// NSLog("Success : %ld ", username)
// if (username != nil)
// {
print("Login OK")
}
else{
print("Login Failed")
let alert = UIAlertController(title: "Invalid", message: "Invalid Credentials", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
}
catch {
print("Login Failed")
let alert = UIAlertController(title: "Error", message: "Please check your internet connection", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
})
//executing the task
task.resume()
}
}
}
Your issue there
let username = parseJSON["username"] as? String
It should be read profile dictionary first
if let profile = parseJSON["Profile"] as? [String:Any],
let username = profile["username"] as? String
Here Full code:
let task = URLSession.shared.dataTask(with: request as URLRequest, completionHandler: {data, response, error -> Void in
DispatchQueue.main.async {
if(error != nil)
{
//Display an alert message
let myAlert = UIAlertController(title: "Alert", message: error!.localizedDescription, preferredStyle: UIAlertControllerStyle.alert);
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.default, handler:nil)
myAlert.addAction(okAction);
self.present(myAlert, animated: true, completion: nil)
}
//parsing the response
do {
//converting resonse to NSDictionary
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
if let parseJSON = json {
if let profile = parseJSON["Profile"] as? [String:Any],
let username = profile["username"] as? String
{
UserDefaults.standard.set(parseJSON["username"], forKey: "username")
UserDefaults.standard.set(parseJSON["password"], forKey: "password")
UserDefaults.standard.synchronize()
// let username : NSInteger = json?.value(forKey: username as String)as! NSInteger
// NSLog("Success : %ld ", username)
// if (username != nil)
// {
print("Login OK")
}
else{
print("Login Failed")
let alert = UIAlertController(title: "Invalid", message: "Invalid Credentials", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
}
catch {
print("Login Failed")
let alert = UIAlertController(title: "Error", message: "Please check your internet connection", preferredStyle: UIAlertControllerStyle.alert)
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.default, handler: nil))
self.present(alert, animated: true, completion: nil)
}
}
})
//executing the task
task.resume()
}
}
}

resend your request with the updated header Transmission in Swift

I've this code to take the X-Transmission-Session-Id but now i need to resend the request with the updated header.
function takeXTransmissionSessionId( ){
let urls = NSURL(string: "\(url)")
let task = NSURLSession.sharedSession().dataTaskWithURL(urls!) { (data, respornse, error) -> Void in
guard error == nil && data != nil else {
print("error=\(error!)")
let alertView = UIAlertController(title: "ERROR", message: "A server with the specified hostname could not be found.", preferredStyle: UIAlertControllerStyle.Alert)
let actionView = UIAlertAction(title: "OK", style: UIAlertActionStyle.Cancel, handler: nil)
alertView.addAction(actionView)
self.presentViewController(alertView, animated: true, completion: nil)
return
}
if let httpStatus = respornse as? NSHTTPURLResponse where httpStatus.statusCode != 200 {
let sesionID = httpStatus.allHeaderFields["X-Transmission-Session-Id"]
if sesionID == nil {
let alertView = UIAlertController(title: "ERROR", message: "The request has not been applied because it lacks valid authentication credentials for the target resource.", preferredStyle: UIAlertControllerStyle.Alert)
let actionView = UIAlertAction(title: "OK", style: UIAlertActionStyle.Cancel, handler: nil)
alertView.addAction(actionView)
self.presentViewController(alertView, animated: true, completion: nil)
}else{
//print("\(respornse!)")
//print("\(sesionID!)")
self.XTransmissionSessionId = "\(sesionID!)"
}
}
}
task.resume()
}
My problem is i tray many times in a different ways but i don't know what i need to do.
Thanks!
This is the code for do what i need:
request.HTTPMethod = "POST"
request.addValue("\(XTransmissionSessionId)", forHTTPHeaderField: "X-Transmission-Session-Id")

stop animating ActivityViewIndicator after invalid login

I'm new to iOS programming and currently is playing around with some tutorial found online. I was trying to include an ActivityViewIndicator in the sign in view. When the "Sign In" button is tapped, an ActivityViewIndicator should show up and it show be hidden when sign in is invalid. My problem is where should i put the self.signInViewIndicator.stopAnimating(); when the sign in is invalid? I have enabled the Hides When Stopped option.
#IBAction func SignInButtonTapped(sender: AnyObject) {
let userUsername = userUsernameTextField.text
let userPassword = userPasswordTextField.text
if(userUsername.isEmpty || userPassword.isEmpty) { return}
signInViewIndicator.startAnimating()
// Send user data to server side
let myUrl = NSURL(string: "http://192.168.168.135:8080/userLogin.php")
let request = NSMutableURLRequest(URL:myUrl!)
request.HTTPMethod = "POST"
let postString = "username=\(userUsername)&password=\(userPassword)"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
if error != nil {
println("error=\(error)")
return
}
var err: NSError?
var json = NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers, error: &err) as? NSDictionary
if let parseJSON = json {
var resultValue = parseJSON["status"] as? String
println("result: \(resultValue)")
var isUserSignedIn:Bool = false
if(resultValue=="Success") {
isUserSignedIn = true
// Login is successful
NSUserDefaults.standardUserDefaults().setBool(isUserSignedIn, forKey: "isUserLoggedIn")
NSUserDefaults.standardUserDefaults().synchronize()
self.dismissViewControllerAnimated(true, completion: nil)
} else {
self.signInViewIndicator.stopAnimating()
var messageToDisplay:String = parseJSON["message"] as! String!
if(!isUserSignedIn)
{
messageToDisplay = parseJSON["message"] as! String!
}
dispatch_async(dispatch_get_main_queue(), {
// Display alert message with confirmation
var myAlert = UIAlertController(title: "Alert", message: messageToDisplay, preferredStyle: UIAlertControllerStyle.Alert)
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler:nil)
myAlert.addAction(okAction);
self.presentViewController(myAlert, animated: true, completion: nil)
})
}
}
}
task.resume()
}
You almost got it rigth, you can never update the UI from a thread other than the main thread, as you was already displaying the alert in the main thread I just had to move your message "self.signInViewIndicator.stopAnimating()" to inside that dispatch block:
dispatch_async(dispatch_get_main_queue(), {
// Display alert message with confirmation
self.signInViewIndicator.stopAnimating()
var myAlert = UIAlertController(title: "Alert", message: messageToDisplay, preferredStyle: UIAlertControllerStyle.Alert)
let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler:nil)
myAlert.addAction(okAction);
self.presentViewController(myAlert, animated: true, completion: nil)
})

Resources