Swift 3 - Activity Indicator delay or not responding - json

I have field and button when you fill the field with content and click the button:
#IBAction func dpSomething(_ sender: UIButton) {
activityIndicator.startAnimating()
activityIndicator.isHidden = false
field.isHidden = true
send.isHidden = true
let url = "http://urlwithjson"
let session = URLSession.shared
DispatchQueue.main.async { [unowned self] in
let task = session.dataTask(with: URL(string: url)!) { (data, response, error) in
if(error==nil){
do {
let jsonData = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! NSDictionary
if((jsonData["response"] as! Int) == 0){
self.responseL.text = "no results"
}
self.activityIndicator.stopAnimating()
self.responseL.isHidden = false
} catch _ {}
}
}
task.resume()
}
}
when I use print it prints me almost immediately the JSON, but activity indicator needs 10-20 secs to disappear or sometimes it doesn't disappear. Can somebody tells me why? And also when I change the text in the label I receive error in console:
This application is modifying the autolayout engine from a background thread, which can lead to engine corruption and weird crashes. This will cause an exception in a future release.
Why this is happening ?

First of all Add the following code after activityIndicator.startAnimating() , this code will hide the activity indicator ones you call self.activityIndicator.stopAnimating():
activityIndicator.hidesWhenStopped = true
Also you are only hiding the activity indicator if the response succeeded, so you may need to hide the indicator when there is an error in catch block and in else block when you get an error from api response
Another point is when you make your request it's not supposed to be in main thread, only in background and then when you need to update the UI then do use main thread , so finally here is the full code you may need:
#IBAction func dpSomething(_ sender: UIButton) {
activityIndicator.startAnimating()
activityIndicator.isHidden = false
activityIndicator.hidesWhenStopped = true
field.isHidden = true
send.isHidden = true
let url = "http://urlwithjson"
let session = URLSession.shared
let task = session.dataTask(with: URL(string: url)!) { (data, response, error) in
if(error==nil){
do {
let jsonData = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! NSDictionary
if((jsonData["response"] as! Int) == 0){
DispatchQueue.main.async {
//Update your UI here
self.responseL.text = "no results"
}
}
DispatchQueue.main.async {
//Update your UI here
self.activityIndicator.stopAnimating()
self.responseL.isHidden = false
}
} catch _ {
// JSONSerialization error might happen so hide the indicator
DispatchQueue.main.async {
//Update your UI here
self.activityIndicator.stopAnimating()
self.responseL.isHidden = false
}
}
}else {
// api response error might happen so hide the indicator
DispatchQueue.main.async {
//Update your UI here
self.activityIndicator.stopAnimating()
self.responseL.isHidden = false
}
}
}
task.resume()
}

You need to only make UI changes on main thread never call Async API call on main thread.
let task = session.dataTask(with: URL(string: url)!) { (data, response, error) in
if(error==nil){
do {
let jsonData = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as! NSDictionary
if((jsonData["response"] as! Int) == 0){
self.responseL.text = "no results"
}
DispatchQueue.main.async {
self.activityIndicator.stopAnimating()
self.activityIndicator.isHidden = true
self.responseL.isHidden = false
}
} catch _ {}
}
}
task.resume()

Related

Parsed JSON not updating on api call for a long time

For some reason the JSON object from parsing doesnt update after network calls to and api we built. I check the endpoint and now for a fact it updates right away. I have a timer being called every 10 sec to make the call but the parsed json doesnt update until after a minute or so. I have tried putting it on the main thread and that still doesnt work. Here is my code:
#objc func getLaunches() {
let simulator = UserDefaults.standard.string(forKey: self.launchSimulator)
if(simulator == self.password){
print("they are the same")
}
guard let launchUrl = URL(string: launchesURL) else {
return
}
let request = URLRequest(url: launchUrl)
DispatchQueue.main.async { [weak self] in
let task = URLSession.shared.dataTask(with: request, completionHandler: {
(data, response, error) -> Void in
if let error = error {
print(error)
return
}
// Parse JSON data
if let data = data {
self?.launches.removeAll()
self?.launches = (self!.parseJsonData(data: data))
let nextlaunch = self?.launches[0]
// Reload table view
self?.hours = nextlaunch?.time
self?.yearMonth = nextlaunch?.date
var fulltime = self?.yearMonth
fulltime!.insert("-", at: fulltime!.index(fulltime!.startIndex, offsetBy: 4))
fulltime!.insert("-", at: fulltime!.index(fulltime!.startIndex, offsetBy: 7))
fulltime = fulltime! + " "
fulltime = fulltime! + self!.hours
let fullFormatter = DateFormatter()
fullFormatter.dateFormat = "YYYY-MM-dd HH:mm"
fullFormatter.timeZone = TimeZone(abbreviation: "EST")
self?.launchDate = fullFormatter.date(from: fulltime!)
self?.getCountdown()
}
})
task.resume()
}
}
//parse launch info from json to dictionary into launches object
func parseJsonData(data: Data) -> [NextLaunch] {
var launches = [NextLaunch]()
do {
let jsonResult = try JSONSerialization.jsonObject(with: data, options:
JSONSerialization.ReadingOptions.allowFragments) as? NSDictionary
let jsonLaunches = jsonResult?["launches"] as! [NSDictionary]
for jsonLaunch in jsonLaunches {
let launch = NextLaunch()
launch.date = jsonLaunch["date"] as! String
launch.time = jsonLaunch["time"] as! String
if(launch.time == ""){
launch.time = "00:00"
}
launch.mission = jsonLaunch["mission"] as! String
launch.launchpad = jsonLaunch["launch_pad"] as! String
launch.image = jsonLaunch["image"] as! String
launch.delay = jsonLaunch["delayed"] as! String
//show delay image if it is delayed
if(launch.delay == "1"){
self.delayed()
}else{
self.notDelayed()
}
launches.append(launch)
}
} catch {
print(error)
}
return launches
}
You need
DispatchQueue.main.async {
self?.getCountdown()
}
As the response of URLSession.shared.dataTask(with: occurs in a background thread

Swift login screen performing segue

I am using following code to login users first user enters password and id then using post request I am sending this information to the server.
let alert = UIAlertController(title: nil, message: "Please wait...", preferredStyle: .alert)
let message2 = "Please wait..."
var messageMutableString = NSMutableAttributedString()
messageMutableString = NSMutableAttributedString(string: message2 as String, attributes: [NSFontAttributeName:UIFont(name: "HelveticaNeue-Bold",size: 15.0)!])
alert.setValue(messageMutableString, forKey: "attributedMessage")
let loadingIndicator = UIActivityIndicatorView(frame: CGRect(x: 10, y: 5, width: 50, height: 50))
loadingIndicator.hidesWhenStopped = true
loadingIndicator.activityIndicatorViewStyle = UIActivityIndicatorViewStyle.gray
loadingIndicator.startAnimating();
alert.view.addSubview(loadingIndicator)
present(alert, animated: true, completion: nil)
if Reachability.isConnectedToNetwork() == true {
var postString = GlobalVariable.globalIpAdress
postString.append("&userid=")
postString.append(userId.text!)
postString.append("&password=")
postString.append(password.text!)
postString.append("&parola=")
let urlString = postString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlFragmentAllowed)
var request = URLRequest(url: URL(string:urlString!)!)
request.httpMethod = "POST"
request.timeoutInterval=10
message="Request timed out"
let task = URLSession.shared.dataTask(with: request) { data, response, error in
DispatchQueue.main.async {
self.dismiss(animated: false, completion: nil)
}
guard let data = data, error == nil else {
print("error=\(String(describing: error))")
DispatchQueue.main.async {
let alert = UIAlertView(title: "Uyarı!", message: self.message, delegate: nil, cancelButtonTitle: "OK")
alert.show()
}
return;
}
if let json = (try? JSONSerialization.jsonObject(with: data, options: [])) as? NSDictionary
{
print(json)
if (json["error"] as? String) != nil {}
if let messages = json["messages"] as? NSArray{
for item in messages {
if let description = item as? AnyObject {
if let text = description["text"] as? String {
self.message = text
}
}
}
}
if let tokenTemp = json["token"] as? String {
self.success=true
GlobalVariable.globalToken = tokenTemp
}
}
if let httpStatus = response as? HTTPURLResponse, httpStatus.statusCode != 200 {
print("statusCode should be 200, but is \(httpStatus.statusCode)")
print("response = \(String(describing: response))")
}
let responseString = String(data: data, encoding: .utf8)
print("responseString = \(String(describing: responseString))")
DispatchQueue.main.async {
if(self.success == true){
self.performSegue(withIdentifier: "secondVC", sender: self)
} else {
let alert = UIAlertView(title: "Warning!", message: self.message, delegate: nil, cancelButtonTitle: "OK")
alert.show()
}
}
}
task.resume()
}
else{
print("Internet connection FAILED")
let alert = UIAlertView(title: "No Internet Connection", message: "Make sure your device is connected to the internet.", delegate: nil, cancelButtonTitle: "OK")
alert.show()
}
First of all, I am adding indicator view for login screen and then, as soon as token came my Bool success variable is becoming true and I am dismissing the activity indicator then it needs to be gone next screen but it is not working right now and also I am getting following error.
Warning: Attempt to present UITabBarController on UIAlertController: whose view is not in the window hierarchy!
What could be the reason where am I doing wrong?
I think the error itself explains the issue here:
Attempt to present UITabBarController on UIAlertController: whose view is not in the window hierarchy!
As you are presenting the UIAlertController in this line of code :
present(alert, animated: true, completion: nil)
but you are not dismissing it and trying to performSegue on UIAlertController:
self.performSegue(withIdentifier: "secondVC", sender: self)
Dismiss the UIAlertController whenever you present it.
Refer this :
How to programmatically dismiss UIAlertController without any buttons?

Wait until JSON call is finished

I have this code in a part of my app:
let myUrl = NSURL(string: "http://aesolutions.ddns.net/data/load_tasks_calendar.php");
let request = NSMutableURLRequest(url: myUrl! as URL);
request.httpMethod = "POST";
let postString = "id_class=\(UserDefaults.standard.string(forKey: "userClass")!)";
request.httpBody = postString.data(using: String.Encoding.utf8);
MenuViewController.tasksCalendarArray.removeAll()
let task = URLSession.shared.dataTask(with: request as URLRequest){
data, response, error in
if error != nil{
print("error1=\(String(describing: error))")
return
}
var _: NSError?
do{
let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSArray
if let parseJSON: NSArray = json {
for index in 0...parseJSON.count-1 {
if (parseJSON[index] is NSNull){
MenuViewController.tasksCalendarArray.removeAll()
}else{
let compito = parseJSON[index] as! [String:Any]
let task = tasks.init(taskId: compito["id"] as! String,taskType: compito["type"] as! String,taskSubject: compito["subject"] as! String,taskDate: compito["date"] as! String,taskComment: compito["comment"] as! String)
MenuViewController.tasksCalendarArray.append(task)
}
}
}
}catch{
print("error2=\(error)")
return
}
DispatchQueue.main.async(execute: {
self.performSegue(withIdentifier: "loginToDiary", sender: self)
});
}
task.resume();
I want to perform the segue only when I load all the array. How can I wait until the json is terminated or is done correctly? Because I noticed that sometimes it is correct and other times it performs the segue and so the array is empty and then in the app there are errors.
Can someone help me also adding an alert with a kind of "loading message" to wait that the array is loading?

Swift3 - How to access variables in closure

Im trying to access the variable pic after the request is made but its in a closure, thats why print(pic) doesn't work.
How would someone go about accessing this?
guard let url = URL(string: "myurl") else{ return }
var pic = ""
let session = URLSession.shared
session.dataTask(with: url) { (data, response, error) in
if let response = response {
print(response)
}
if let data = data {
print(data)
do {
let json = try JSONSerialization.jsonObject(with: data, options: .mutableContainers) as? NSDictionary
if let parseJSON = json {
pic = parseJSON["picture"] as! String
print(json!)
}
} catch {
print(error)
}
}
}.resume()
print(pic)
}
Assuming pic is an image you'll be loading into a UIImageView:
You can add an Activity Indicator to your ImageView. Then when you call your function to download the pic simply add:
guard let url = URL(string: "myurl") else{ return }
activityIndicator.isHidden = false
activityIndicator.startAnimating()
The user will know a download is occurring. When complete,
DispatchQueue.main.async {
activityIndicator.isHidden = true
activityIndicator.stopAnimating()
myImageView.image = UIImage(named: "pic")
}
}.resume
Dispatching on the main que will update the UI immediately.

I'm having troubles with my Login authentication

I have a JSON Post authentication to make a login request, of course I need to send the user & password parameters to receive a response for filling my HomeViewController, so I need first to validate the two parameters, and if the user doesn't exist of course I'm going to send an Alert and forbid the access to the Home. The thing is that I don't know in which method do I have to put this validation, because the way I'm doing it throws me an alert before the validation. This is my code:
class LoginViewController: UIViewController, LoginProtocol {
#IBOutlet weak var username: UITextField!
#IBOutlet weak var password: UITextField!
#IBOutlet weak var activityIndicator: UIActivityIndicatorView!
var user : String = ""
var psw : String = ""
var idResponse : String = ""
var message : String = ""
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func validateLogin(_ sender: Any) {
DoLogin(self.username.text!, self.password.text!)
}
#IBAction func login(_ sender: Any) {
if self.idResponse != "0" {
validation(message: self.message)
} else {
let vc = self.storyboard!.instantiateViewController(withIdentifier: "home") as! HomeViewController
self.present(vc, animated: false, completion: nil)
vc.getUser = self.username.text!
vc.getPassword = self.password.text!
}
}
func validation(message: String) {
let myAlert = UIAlertController(title: "Alert", message: message, preferredStyle: UIAlertControllerStyle.alert)
let okAction = UIAlertAction (title: "Ok", style: UIAlertActionStyle.default, handler: nil)
myAlert.addAction(okAction)
self.present(myAlert, animated: true, completion: nil)
return
}
func DoLogin(_ user:String, _ psw:String)
{
let url = URL(string: "http://162.209.99.39:8080/MiClaroBackend/auth")
let session = URLSession.shared
let request = NSMutableURLRequest(url: url!)
request.httpMethod = "POST"
self.username.text = user
self.password.text = psw
let paramToSend = ["username":user,"password":psw]
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try! JSONSerialization.data(withJSONObject: paramToSend)
let task = session.dataTask(with: request as URLRequest, completionHandler: {
(data, response, error) in
guard let _:Data = data else
{
return
}
let json:AnyObject?
do {
json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
if let parseJSON = json{
let loginModel = LoginModel()
let defaultServiceResponse = parseJSON["defaultServiceResponse"] as! NSDictionary
loginModel.message = defaultServiceResponse["message"] as! String
loginModel.idResponse = defaultServiceResponse["idResponse"] as! Int
self.idResponse = String(loginModel.idResponse)
self.message = loginModel.message
print(json!)
}
} catch {
let error = ErrorModel()
error.phrase = "PARSER_ERROR"
error.code = -1
error.desc = "Parser error in login action"
}
})
task.resume()
}
The validateLogin method is attached to a TextView with the action: "EditingDidEnd". So what I need is that when I finish writing the password, the DoLogin function validates that exists and let me continue to the HomeViewController, but the issue is that when I click the button 'Ingresar'(Login), it shows a blank Alert first and only the second time I click the button it let me through immediately. I don't know why is sending the Alert that I declare at the "func validation" if I'm not calling it before. I'm going to attach a picture to show you the Alert that is appearing:
Chances are your EditingDidEnd method calls DoLogin which is processing in background when you press the Login button. hence your if condition below becomes true as your idResponse is initialized to "" empty string.
if self.idResponse != "0"
Rectify this if condition to check if its empty then dont call the validation message.
The best way to do this is to call your DoLogin only after click of the Login button and if in case you have success redirect to Home page. Otherwise show the alert. Please note do the UI action on the main thread.
First Update the Code And User Like this
func DoLogin(_ user:String, _ psw:String)
{
let url = URL(string: "http://162.209.99.39:8080/MiClaroBackend/auth")
let session = URLSession.shared
let request = NSMutableURLRequest(url: url!)
request.httpMethod = "POST"
let paramToSend = ["username":user,"password":psw]
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.httpBody = try! JSONSerialization.data(withJSONObject: paramToSend)
let task = session.dataTask(with: request as URLRequest, completionHandler: {
(data, response, error) in
guard let _:Data = data else
{
return
}
let json:AnyObject?
do {
json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary
if let parseJSON = json{
let loginModel = LoginModel()
let defaultServiceResponse = parseJSON["defaultServiceResponse"] as! NSDictionary
loginModel.message = defaultServiceResponse["message"] as! String
loginModel.idResponse = defaultServiceResponse["idResponse"] as! Int
self.idResponse = String(loginModel.idResponse)
self.message = loginModel.message
if self.idResponse != "0" {
validation(message: self.message)
} else {
let vc = self.storyboard!.instantiateViewController(withIdentifier: "home") as! HomeViewController
self.present(vc, animated: false, completion: nil)
vc.getUser = user!
vc.getPassword = psw!
}
}
} catch {
let error = ErrorModel()
error.phrase = "PARSER_ERROR"
error.code = -1
error.desc = "Parser error in login action"
}
})
task.resume()
}
If You want to add alert in imageview use this code
var imageView = UIImageView(frame: CGRectMake(220, 10, 40, 40))
imageView.image = yourImage
alert.view.addSubview(imageView)