iOS Swift 3 - Can't stop the UIActivityIndicatorView after submit - ios

I have a submit button which submits email and password for validation through HTTP in JSON format, but indicator view does not stop even though the app receives the response from the server.
#IBAction func signInButtonAction(_ sender: UIButton) {
let validated = validateEmailAddressTextField()
if validated {
let emailAddress = emailAddressTextField.text
let password = passwordField.text
let url:URL = URL(string: "http://localhost:8080/app/user/signin")!
let session = URLSession.shared
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.allHTTPHeaderFields = ["Content-Type": "application/json"]
request.cachePolicy = NSURLRequest.CachePolicy.reloadIgnoringCacheData
let user = User(emailAddress: emailAddress!, password: password!)
let requestJSON = careGiver.toJSON()
request.httpBody = requestJSON?.data(using: String.Encoding.utf8)
let indicator = UIActivityIndicatorView(activityIndicatorStyle: .gray)
indicator.center = view.center
view.addSubview(indicator)
indicator.startAnimating()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.0) {
let task = session.dataTask(with: request as URLRequest) {
(
data, response, error) in
guard let data = data, let _:URLResponse = response, error == nil else {
print("error")
return
}
let dataString = String(data: data, encoding: String.Encoding.utf8)
print(dataString)
indicator.stopAnimating()
}
task.resume()
}
}
}
When I click 'submit' button, I see an indicator view on top of the button, but it doesn't stop and disappear.
Is there anything wrong with the code?
I am using Swift 3 and Xcode 8.
Thanks.

indicator.stopAnimating()
Try to run this line of code on the main thread:
DispatchQueue.main.async {
indicator.stopAnimating()
}

Network closure is called on a random thread, but UI code must be handled in main thread only. So all you need is to call your indicator.stopAnimating() in the main thread:
DispatchQueue.main.async {
indicator.stopAnimating()
}
Also I'd move this block to the beginning of the closure (before guard) to prevent continue of the animation in case of error in response.

Related

How can I stop URLSessionTask when the Internet is disconnected?

I am using URLSessionTask to get the source code of url. When the internet is connected, it works well.
However, when the Internet is disconnected, I try building. And in simulator it is blank and the cpu is 0%. What affects is that My Tab Bar Controller is also missing and blank (It is my initial view controller). It seems that this task is under connecting?
I want the data received from dataTask, so I use semaphore to make it synchronous. Otherwise, as dataTask is an asynchronous action, what I
get is an empty string.
How can I fix this problem?
Thanks!
let urlString:String="http://www.career.fudan.edu.cn/jsp/career_talk_list.jsp?count=50&list=true"
let url = URL(string:urlString)
let request = URLRequest(url: url!)
let session = URLSession.shared
let semaphore = DispatchSemaphore(value: 0)
let dataTask = session.dataTask(with: request,
completionHandler: {(data, response, error) -> Void in
if error != nil{
errorString = "Error!"
}else{
htmlStr = String(data: data!, encoding: String.Encoding.utf8)!
//print(htmlStr)
}
semaphore.signal()
}) as URLSessionTask
//start task
dataTask.resume()
_ = semaphore.wait(timeout: DispatchTime.distantFuture)
Update: As #Moritz mentioned, I finally use completion handler (callback).
func getforData(completion: #escaping (String) -> ()) {
if let url = URL(string: "http://XXXXX") {
let request = URLRequest(url: url)
let task = URLSession.shared.dataTask(with: request) {
data, response, error in
if let data = data, let getString = String(data: data, encoding: String.Encoding.utf8), error == nil {
completion(getString)
} else {
print("error=\(error!.localizedDescription)")
}
}
task.resume()
}
}
And in viewdidload
override func viewDidLoad() {
super.viewDidLoad()
getforData { getString in
// and here we get the "returned" value from the asynchronous task
print(getString) //works well
//tableview should work in main thread
DispatchQueue.main.async {
self.newsTableView.dataSource = self
self.newsTableView.delegate = self
self.newsTableView.reloadData()
}
}

Swift rest client is making slow connections

I have a performance problem implementing a REST client related with long delays. Problem is not present on my Android app or any REST client adding to browsers so it is not related with backend issues.
this is my function for executing API call:
func getRecordsMethod(completion: #escaping ((_ result: Data) -> Void)){
let defaults = UserDefaults.standard
let username = defaults.object(forKey: "username") as! String
let password = defaults.object(forKey: "password") as! String
let loginString = String(format: "%#:%#", username, password).description
let loginData = loginString.data(using: String.Encoding.utf8)
let base64LoginString = loginData?.base64EncodedString()
let url = URL (string: apiURL + getPersonalBests )
var request = URLRequest(url: url!)
request.httpMethod = "GET"
request.setValue("Basic " + base64LoginString!, forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode == 200 {
completion(data!)
}
}
task.resume()
}
and how I call it in my ViewController :
override func viewDidLoad() {
super.viewDidLoad()
populateRecords()
}
func populateRecords(){
self.records.removeAll()
apiRestManager.getRecordsMethod() {
(result: Data) in
let json = JSON(data: result)
for (_, subJson) in json {
// processing response- adding to personalBestTable
}
DispatchQueue.main.async {
self.personalBestTable.reloadData()
}
}
}
The biggest issue is that after function populateRecords is being started there is delay approx 2-3 seconds after a call is visible on my backend (after that processing and table reload is made instantly)
Can anyone give me a hint how I can optimize requests speed?
Getting the data from UserDefault on main threat may cause the delay.
Change your viewDidLoad method with following code
override func viewDidLoad() {
super.viewDidLoad()
let concurrentQueue = DispatchQueue(label: "apiQueue", attributes: .concurrent)
concurrentQueue.async {
populateRecords()
}
}

PushViewController doesn't work after httpPost

I have made a login screen which takes the input and communicates with the REST api to verify the user. If the response is true, I login the user else not.
I have written a method openViewControllerBasedOnIdentifier(id) to switch views.
The REST api returns true and false appropriately. The push controller gets called but view does not change. How if I place only one line in LoginAction method 'self.openViewControllerBasedOnIdentifier("PlayVC")' and remove the rest of the code , it works fine.
Here is my code
#IBAction func LoginAction(_ sender: Any) {
//self.openViewControllerBasedOnIdentifier("PlayVC")
Constants.login_status = false
//created NSURL
let requestURL = NSURL(string: URL_BK)
//creating NSMutableURLRequest
let request = NSMutableURLRequest(url: requestURL! as URL)
//setting the method to post
request.httpMethod = "POST"
let username = phonenumber.text
//creating the post parameter by concatenating the keys and values from text field
let postParameters = "username="+username!+"&password=bk&schoolId=0";
//adding the parameters to request body
request.httpBody = postParameters.data(using: String.Encoding.utf8)
//creating a task to send the post request
let task = URLSession.shared.dataTask(with: request as URLRequest){
data, response, error in
let responseData = String(data: data!, encoding: String.Encoding.utf8)
if error != nil{
print("error is \(error)")
return;
}
//parsing the response
do {
print(“Received data is ---%#",responseData as Any)
let myJSON = try JSONSerialization.jsonObject(with: data! , options: .allowFragments) as? NSDictionary
if let parseJSON = myJSON {
var status : Bool!
status = parseJSON["status"] as! Bool?
//print(status)
if status==false
{
Constants.login_status = false
}
else{
Constants.login_status = true
print("calling PLAYVC")
self.openViewControllerBasedOnIdentifier("PlayVC")
}
}
else{
print("NULL VALUE RECEIVED")
}
} catch {
print(error)
}
}
//executing the task
task.resume()
}
You should open the new view controller on the main thread like this:
DispatchQueue.main.async {
self.openViewControllerBasedOnIdentifier("PlayVC")
}
Your REST API query response is processed in a background thread when you call URLSession.shared.dataTask and so when you call any UI actions, you should wrap the code as above to execute the UI code in the main thread. Then it would work fine :)

Synchronous API request to Asynchronous API request Swift 2.2

Well I am new to Swift and I don't know much of completion handler. I want to get a request from an API and parse the JSON response so I can get the token. But what's happening with my code is that whenever I call the getAuthentication function my UI freezes and waiting for the data to get. Here is the code for getAuthentication
func getAuthentication(username: String, password: String){
let semaphore = dispatch_semaphore_create(0);
let baseURL = "Some URL here"
let url = NSURL(string: baseURL)!
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
request.HTTPBody = "{\n \"username\": \"\(username)\",\n \"password\": \"\(password)\"\n}".dataUsingEncoding(NSUTF8StringEncoding);
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
if error == nil{
let swiftyJSON = JSON(data: data!)
print(swiftyJSON)
//parse the data to get the user
self.id = swiftyJSON["id"].intValue
self.token = swiftyJSON["meta"]["token"].stringValue
} else {
print("There was an error")
}
dispatch_semaphore_signal(semaphore);
}
task.resume()
dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
}
then, I am calling this method in my LoginViewController. Someone says that I am using a Synchronous request thats why my UI freezes, but I have really no idea on how to change it to Async and wait for the data to be downloaded. Can someone help me with this? Any help will much be appreciated.
Firstly, remove dispatch_semaphore related code from your function.
func getAuthentication(username: String, password: String){
let baseURL = "Some URL here"
let url = NSURL(string: baseURL)!
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
request.HTTPBody = "{\n \"username\": \"\(username)\",\n \"password\": \"\(password)\"\n}".dataUsingEncoding(NSUTF8StringEncoding);
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
if error == nil{
let swiftyJSON = JSON(data: data!)
print(swiftyJSON)
//parse the data to get the user
self.id = swiftyJSON["id"].intValue
self.token = swiftyJSON["meta"]["token"].stringValue
} else {
print("There was an error")
}
}
task.resume()
}
In the above code, the function dataTaskWithRequest itself is an asynchronus function. So, you don't need to call the function getAuthentication in a background thread.
For adding the completion handler,
func getAuthentication(username: String, password: String, completion:((sucess: Bool) -> Void)){
let baseURL = "Some URL here"
let url = NSURL(string: baseURL)!
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "POST"
request.HTTPBody = "{\n \"username\": \"\(username)\",\n \"password\": \"\(password)\"\n}".dataUsingEncoding(NSUTF8StringEncoding);
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
var successVal: Bool = true
if error == nil{
let swiftyJSON = JSON(data: data!)
print(swiftyJSON)
self.id = swiftyJSON["id"].intValue
self.token = swiftyJSON["meta"]["token"].stringValue
} else {
print("There was an error")
successVal = false
}
dispatch_async(dispatch_get_main_queue(), { () -> Void in
completion(successVal)
})
}
task.resume()
}
It can be called as follows:
self.getAuthentication("user", password: "password", completion: {(success) -> Void in
})
You may pass an escaping closure argument to getAuthentication method.
func getAuthentication(username: String, password: String, completion: (JSON) -> ()){
...
// create a request in the same way
...
let session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request) { (data, response, error) -> Void in
if error == nil{
let swiftyJSON = JSON(data: data!)
print(swiftyJSON)
completion(swiftyJSON)
} else {
print("There was an error")
}
}
task.resume()
}
And call getAuthentication in LoginViewController like this:
getAuthentication(username, password) { (json) -> in
//Do whatever you want with the json result
dispatch_async(dispatch_get_main_queue()) {
// Do UI updates
}
}
Another way to go is calling getAuthentication in a background thread in your LoginViewController to avoid blocking the main thread (i.e. UI thread).
//In LoginViewController
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
getAuthentication(username, password)
dispatch_async(dispatch_get_main_queue()) {
// UI updates
}
}

swift asynchronous request using dispatch_group_notify doesn't work

I'm trying to use dispatch_group_notify to send a HTTP request where I need to wait for the result of this command before continuing my processing.
here is the following call:
self.save(){(response) in
if let result = response as? Bool {
if(result == true){
dispatch_group_notify(self.myGroup!, dispatch_get_main_queue(), {
print("send carnet finished")
let registrationView = UIStoryboard(name: "Main", bundle: nil).instantiateViewControllerWithIdentifier("carnetTable") as! CarnetTableViewController
self.navigationController?.pushViewController(registrationView, animated: true)
})
}
}
}
and here is the function who is sending the HTTP command:
func save(callback: (AnyObject) -> ()){
dispatch_group_enter(self.myGroup)
let p = pickerDataSource[patients.selectedRowInComponent(0)]
let params = "owner=\(User.sharedInstance.email)&patient=\(p)&carnet=\(commentaires.text!)"
let final_url = url_to_request + "?" + params.stringByAddingPercentEncodingForISOLatin1()!
print("URL addCarnet: \(final_url)")
let url:NSURL = NSURL(string: final_url)!
//let session = NSURLSession.sharedSession()
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: configuration,
delegate: self,
delegateQueue:NSOperationQueue.mainQueue())
let request = NSMutableURLRequest(URL: url)
request.HTTPMethod = "GET"
request.cachePolicy = NSURLRequestCachePolicy.ReloadIgnoringCacheData
request.timeoutInterval = 10
let task = session.dataTaskWithRequest(request) {
(
let data, let response, let error) in
guard let _:NSData = data, let _:NSURLResponse = response where error == nil else {
print("error data")
dispatch_group_leave(self.myGroup)
callback(false)
return
}
var result = NSString(data: data!, encoding:NSASCIIStringEncoding)!
print("result: \(result)")
}
task.resume()
dispatch_group_leave(self.myGroup)
callback(true)
}
I would like to ensure that save function is finished (dispatch_group_leave) before opening the new ViewController (CarnetTableViewController) but I can see that ViewController is called before the end of the dispatch_group...
how can I ensure the end of the save function before opening the new View ?
The last three lines of your function:
task.resume()
dispatch_group_leave(self.myGroup)
callback(true)
This causes the task to start, and then you immediately (before the task has completed), leave the group and call the callback.
If you trace through the code, your dispatch_group_enter and dispatch_group_leave occur in the same scope, on the same queue, and before you call callback(). That means they're not actually doing anything. By the time you get to your callback, the dispatch_group is empty.
If you had an error, I'd expect a problem when that error-leg calls dispatch_group_leave a second time (since this is unbalanced).
You meant this:
...
var result = NSString(data: data!, encoding:NSASCIIStringEncoding)!
print("result: \(result)")
dispatch_group_leave(self.myGroup)
callback(true)
}
task.resume()

Resources