I've been trying to get information out of a server call. Which already works fine, but when I want to use the information to make a dialogue box appear it doesn't really work. It goes too fast to the next line. The only way I could make it work is by delaying the next bit in the code but considering it's a server call I can't determine how long it'll always take. I tried using dispatch_sync but it didn't work at all.
func logIn(username: String, password: String) -> Void {
var error: NSError?
var call = "api/usersession/"
var login = "username=" + username + "&password=" + password
API().getLogin(login, api:call, { (data) -> Void in
let json = JSON(data: data, error: &error)
println(json.self)
let status = json["status"].stringValue!
if (status == "1") {
alertController = UIAlertController(title: "Something went wrong", message: json["result"].stringValue, preferredStyle: UIAlertControllerStyle.Alert)
alertController.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
} else {
alertController = UIAlertController(title: "Welcome!", message: "You're signed in", preferredStyle: UIAlertControllerStyle.Alert)
}
})
}
I believe that whenever the code gets to the "getLogin" part it just immediately goes on to return whatever it has (which is always nothing). When I say that self.presentViewController needs to delay by an amount then it does work, but this would make the app feel a bit clunky.
Edit:
func getLogin(login: String, api: String, success: ((apiData: NSData!) -> Void)) {
var request = NSMutableURLRequest(URL: NSURL(string: website + api)!)
var learn = API()
var postString = login
request.HTTPMethod = "POST"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
learn.httpRequest(request, callback:{(data, error) -> Void in
if let urlData = data {
success(apiData: urlData)
}
})
}
And httpRequest:
func httpRequest(request: NSURLRequest!, callback: (NSData?,
String?) -> Void) {
var configuration =
NSURLSessionConfiguration.defaultSessionConfiguration()
var userPassWordString = apiusername + ":" + apipassword
let userPasswordData = userPassWordString.dataUsingEncoding(NSUTF8StringEncoding)
let base64EncodedCrendtial = userPasswordData!.base64EncodedStringWithOptions(nil)
let authString = "Basic \(base64EncodedCrendtial)"
configuration.HTTPAdditionalHeaders = ["Authorization": authString]
var session = NSURLSession(configuration: configuration,
delegate: self,
delegateQueue:NSOperationQueue.mainQueue())
var task = session.dataTaskWithRequest(request){
(data: NSData!, response: NSURLResponse!, error: NSError!) -> Void in
if error != nil {
callback(nil, error.localizedDescription)
} else {
var result = NSData(data: data)
callback(result, nil)
}
}
task.resume()
}
It goes too fast to the next line.
You need to think about asynchronous operations. When you do something that's going to take time, whether it's making a network request to a server or asking the user to provide some information, you don't just make a function call and expect the results to show up immediately. Instead, you call a function or method that starts the process, and then you go off and do other things until the process completes. For a network request, that usually means that you provide a delegate object or a completion block that gets called when the connection has new information. When dealing with the user, you might put up a dialog box and leave it at that -- the process continues when the user triggers an action by tapping some button or filling in some field.
I managed to fix it by making the UIAlertController present itself from the function itself like so:
if (status == "1") {
alertController = UIAlertController(title: "Er ging iets fout", message: json["result"].stringValue, preferredStyle: UIAlertControllerStyle.Alert)
alertController.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(alertController,animated: true, completion:nil)
} else {
alertController = UIAlertController(title: "Welcome!", message: "You're signed in!", preferredStyle: UIAlertControllerStyle.Alert)
alertController.addAction(UIAlertAction(title: "OK", style: UIAlertActionStyle.Default, handler: nil))
UIApplication.sharedApplication().keyWindow?.rootViewController?.presentViewController(alertController,animated: true, completion:nil)
}
Related
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'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")
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)
})