UIAlertView in closures callback function in Swift - swift

I need to login through api when user click login button. The return results might failure or success. I want to use alterview to notice the user.
I defined one global variable in class:
var responseString:String = "";
In button click event:
#IBAction func login(sender: AnyObject) {
sendPostLoginRequest {
results in
println("here:\(results)")
self.responseString = results
}
var myAlert = UIAlertController(title:"Alert", message:self.responseString, preferredStyle: UIAlertControllerStyle.Alert);
let okAction = UIAlertAction(title:"Ok", style:UIAlertActionStyle.Default, handler:nil);
myAlert.addAction(okAction);
self.presentViewController(myAlert, animated:true, completion:nil);
}
The self.responseString is empty, even through i can get it in sendPostLoginRequest function:
func sendPostLoginRequest(completionHandler:(result:String)->()){
let name = usernamefield.text
let password = passwordfield.text
let request = NSMutableURLRequest(URL: NSURL(string: "http://www.xxx.xxx/api/user/login")!)
request.HTTPMethod = "POST"
let postString = "username=\(name)&password=\(password)"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
if error != nil {
let jsonStr = NSString(data: data, encoding: NSUTF8StringEncoding)
}
let json = JSON(data: data)
println("results: \(json) ")
self.responseString = json[0].string!
//json[0].string! can be success or failure
completionHandler(result:self.responseString)
}
task.resume()
}

The way I see it, I think when you click the button, the alertView is shown without the responseString or nil. The problem is you are anticipating the block will get called later on and hence the data for responseString will be set later in time but the code below will continue to execute.
The long way to do is:
//observe this variable and when something changes we will alert it
var responseString:String {
didSet{
//do some checking or other work
presentAlert(responseString)
}
}
#IBAction func login(sender: AnyObject) {
sendPostLoginRequest {
results in
println("here:\(results)")
self.responseString = results
}
}
func presentAlert(msg:String){
var myAlert = UIAlertController(title:"Alert", message:msg, preferredStyle: UIAlertControllerStyle.Alert);
let okAction = UIAlertAction(title:"Ok", style:UIAlertActionStyle.Default, handler:nil);
myAlert.addAction(okAction);
self.presentViewController(myAlert, animated:true, completion:nil);
}
The short way to do is:
#IBAction func login(sender: AnyObject) {
sendPostLoginRequest {
results in
println("here:\(results)")
self.responseString = results
dispatch_async(dispatch_get_main_queue(), { () -> Void in
//update ui from main thread always
self.presentAlert(results) //may pass responseString too
})
}
}
func presentAlert(msg:String){
var myAlert = UIAlertController(title:"Alert", message:msg, preferredStyle: UIAlertControllerStyle.Alert);
let okAction = UIAlertAction(title:"Ok", style:UIAlertActionStyle.Default, handler:nil);
myAlert.addAction(okAction);
self.presentViewController(myAlert, animated:true, completion:nil);
}
Had you put the code below the closure inside it would have worked but you would be running UI things from other threads which are bad so use Dispatch Async to work for long-running operations on a separate thread.
Let me know if that helps and hope it does. Cheers!

Related

UIActivityIndicatorView not showing before the URLSession.shared.dataTask

My UIActivityIndicatorView is not showing when I call it before the API request, and will show after the request has been done,
This is my function that run inside the TouchUpInside of my button
func onLogin () {
let activityIndicator: UIActivityIndicatorView = UIActivityIndicatorView()
activityIndicator.center = view.center
activityIndicator.hidesWhenStopped = true
activityIndicator.style = UIActivityIndicatorView.Style.gray
view.addSubview(activityIndicator)
activityIndicator.startAnimating()
do {
let data = try self.getData()
let loginData = try JSONDecoder().decode(LoginResponse.self, from: data!)
print(loginData)
} catch {
let alert = UIAlertController(title: "Login failed", message: nil, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default))
present(alert, animated: true, completion: nil)
}
}
and my request code was
static func getData() throws -> Data? {
let urlData = URL(string: "www.example.com")
var request : URLRequest = URLRequest(url: urlData!)
request.httpMethod = "POST"
request.httpBody = self.getBodyString.data(using: .utf8)
var data: Data?
var response: URLResponse?
var error: Error?
let semaphore = DispatchSemaphore(value: 0)
URLSession.shared.dataTask(with: request) { d,r,e in
data = d
response = r
error = e
semaphore.signal()
}.resume()
_ = semaphore.wait(timeout: .distantFuture)
if error != nil {
throw error!
}
return data
}
when I remove the do catch with getData() in my onLogin() function the UIActivityIndicatorView was working good.
The response of my API call was request timeout, but I want to see the indicator loading the request.
Try to bring activity indictor to front of UIview and Use it in DispatchQueue.main.async

How to store value and retrive it to use in next view controller after login using userdefaults?

I want to store the ngoid value in userDefaults so that I can access it in my next API call in the next viewController class. How do I do it?
Here is the code I have written:
#IBAction func loginbutton(_ sender: Any) {
let myUrl = NSURL(string: "http://www.shreetechnosolution.com/funded/ngo_login.php")
let request = NSMutableURLRequest(url:myUrl! as URL)
request.httpMethod = "POST"// Compose a query string
let postString = "uname=\(textfieldusername.text!)&password=\(textfieldpassword.text!)";
request.httpBody = postString.data(using: String.Encoding.utf8)
let task = URLSession.shared.dataTask(with: request as URLRequest){ data , response , error in
if error != nil
{
//let alert = UIAlertView()
let alert = UIAlertController(title: "Alert Box !", message: "Login Failed", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
return
}
// You can print out response object
print("*****response = \(String(describing: response))")
let responseString = NSString(data: data! , encoding: String.Encoding.utf8.rawValue )
if ((responseString?.contains("")) == nil) {
print("incorrect - try again")
let alert = UIAlertController(title: "Try Again", message: "Username or Password Incorrect", preferredStyle: .alert)
let yesAction = UIAlertAction(title: "Nochmalversuchen", style: .default) { (action) -> Void in
}
// Add Actions
alert.addAction(yesAction)
// Present Alert Controller
self.present(alert, animated: true, completion: nil)
}
else {
print("correct good")
}
print("*****response data = \(responseString!)")
do {
//create json object from data
if let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary {
if let email = json["UserName"] as? String,
let password1 = json["passowrd"] as? String {
print ("Found User id: called \(email)")
}
let msg = (json.value(forKey: "message") as! NSString!) as String
//let id = json.value(forKey: "NgoId") as! Int!
let ngoid = json.value(forKey: "NgoId") as? String
print(ngoid ?? "")
let defaults = UserDefaults.standard
defaults.set(ngoid, forKey: "ngoid")
print(ngoid!)
DispatchQueue.main.async {
self.alert = UIAlertController(title: "Alert Box!", message: "\(msg)", preferredStyle: .alert)
self.action = UIAlertAction(title: "OK", style: .default) { (action) -> Void in
let vtabbar1 = self.storyboard?.instantiateViewController(withIdentifier: "tabbar1")
self.navigationController?.pushViewController(vtabbar1!, animated: true)
}
self.alert.addAction(self.action)
self.present(self.alert, animated: true, completion: nil)
}
}
}
catch let error {
print(error)
}
}
task.resume()
}
You could use UserDefaults but if you only need to use the value on the next viewController you should use a segue for this purpose. Here is a guide of how that works. Otherwise use UserDefaults like the example below:
// To set the value
UserDefaults.standard.set(ngoid, forKey: "NgoId")
// To get the value
let id = UserDefaults.standard.string(forKey: "NgoId")
This is not a best way to save in user default and then use in next ViewController, use this overdid method
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
if segue.identifier == "ShowCounterSegue"
{
if let destinationVC = segue.destinationViewController as? OtherViewController {
destinationVC.ngoid = ngoid
}
}
}
use ngoid anywhere in your next ViewController api call.

iOS swift 3.0 Json parsing and alert issue

I'm working on login form. I'm a fresher on iOS development.
After successful login, I want to show an alert after completion of json parsing. I've parsed Ngoid inside a do while block. Now I want to pass the value "Ngoid" to the next view controller so that it can be used to fetch the further data.
Main Problem: Here is the code I have written and it gives me error to write alert it on main thread only.
As I want the "Ngoid" value for further use there, so how should I write it and what is the correct way to execute the code?
Here is the code I have written:
#IBAction func loginbutton(_ sender: Any) {
let myUrl = NSURL(string: "http://www.shreetechnosolution.com/funded/ngo_login.php")
let request = NSMutableURLRequest(url:myUrl! as URL)
request.httpMethod = "POST"// Compose a query string
let postString = "uname=\(textfieldusername.text!)&password=\(textfieldpassword.text!)";
request.httpBody = postString.data(using: String.Encoding.utf8)
let task = URLSession.shared.dataTask(with: request as URLRequest){ data , response , error in
if error != nil
{
//let alert = UIAlertView()
let alert = UIAlertController(title: "Alert Box !", message: "Login Failed", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
return
}
// You can print out response object
print("*****response = \(String(describing: response))")
let responseString = NSString(data: data! , encoding: String.Encoding.utf8.rawValue )
if ((responseString?.contains("")) == nil) {
print("incorrect - try again")
let alert = UIAlertController(title: "Try Again", message: "Username or Password Incorrect", preferredStyle: .alert)
let yesAction = UIAlertAction(title: "Nochmalversuchen", style: .default) { (action) -> Void in
}
// Add Actions
alert.addAction(yesAction)
// Present Alert Controller
self.present(alert, animated: true, completion: nil)
}
else {
print("correct good")
}
print("*****response data = \(responseString!)")
do {
//create json object from data
if let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary {
if let email = json["UserName"] as? String,
let password1 = json["passowrd"] as? String {
print ("Found User id: called \(email)")
}
let msg = (json.value(forKey: "message") as! NSString!) as String
let id = (json.value(forKey: "NgoId") as! NSString!) as String
// let alert : UIAlertView = UIAlertView(title: "Alert box!", message: "\(msg!).",delegate: nil, cancelButtonTitle: "OK")
// alert.show()
self.alert = UIAlertController(title: "Alert Box!", message: "\(msg)", preferredStyle: .alert)
print("the alert\(self.alert)")
self.action = UIAlertAction(title: "OK", style: .default) { (action) -> Void in
let viewControllerYouWantToPresent = self.storyboard?.instantiateViewController(withIdentifier: "pass1") as! ViewControllerngodetails
viewControllerYouWantToPresent.temp1 = self.id
self.present(viewControllerYouWantToPresent, animated: true, completion: nil)
}
self.alert.addAction(self.action)
self.present(self.alert, animated: true, completion: nil)
}
}catch let error {
print(error)
}
}
task.resume()
}
A pro tip:
All your UI related tasks need to be done in the main thread. Here you are presenting the alert inside a closure which executes in a background thread, thats the problem. You need to call the main queue and present alert in that block.
EDIT:
Just put your alert code in this-
For Swift 3-
Get main queue asynchronously
DispatchQueue.main.async {
//Code Here
}
Get main queue synchronously
DispatchQueue.main.sync {
//Code Here
}
Every UI update has to be on main thread:
#IBAction func loginbutton(_ sender: Any) {
let myUrl = NSURL(string: "http://www.shreetechnosolution.com/funded/ngo_login.php")
let request = NSMutableURLRequest(url:myUrl! as URL)
request.httpMethod = "POST"// Compose a query string
let postString = "uname=\(textfieldusername.text!)&password=\(textfieldpassword.text!)";
request.httpBody = postString.data(using: String.Encoding.utf8)
let task = URLSession.shared.dataTask(with: request as URLRequest){ data , response , error in
if error != nil
{
DispatchQueue.main.async {
let alert = UIAlertController(title: "Alert Box !", message: "Login Failed", preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "OK", style: .default, handler: nil))
// Present Alert Controller
self.present(alert, animated: true, completion: nil)
}
return
}
// You can print out response object
print("*****response = \(String(describing: response))")
let responseString = NSString(data: data! , encoding: String.Encoding.utf8.rawValue )
if ((responseString?.contains("")) == nil) {
print("incorrect - try again")
DispatchQueue.main.async {
let alert = UIAlertController(title: "Try Again", message: "Username or Password Incorrect", preferredStyle: .alert)
let yesAction = UIAlertAction(title: "Nochmalversuchen", style: .default) { (action) -> Void in }
// Add Actions
alert.addAction(yesAction)
// Present Alert Controller
self.present(alert, animated: true, completion: nil)
}
}
else {
print("correct good")
}
print("*****response data = \(responseString!)"
do {
//create json object from data
if let json = try JSONSerialization.jsonObject(with: data!, options: .mutableContainers) as? NSDictionary {
if let email = json["UserName"] as? String,
let password1 = json["passowrd"] as? String {
print ("Found User id: called \(email)")
}
let msg = (json.value(forKey: "message") as! NSString!) as String
let id = (json.value(forKey: "NgoId") as! NSString!) as String
DispatchQueue.main.async {
self.alert = UIAlertController(title: "Alert Box!", message: "\(msg)", preferredStyle: .alert)
print("the alert\(self.alert)")
self.action = UIAlertAction(title: "OK", style: .default) { (action) -> Void in
let viewControllerYouWantToPresent = self.storyboard?.instantiateViewController(withIdentifier: "pass1") as! ViewControllerngodetails
viewControllerYouWantToPresent.temp1 = self.id
self.present(viewControllerYouWantToPresent, animated: true, completion: nil)
}
self.alert.addAction(self.action)
self.present(self.alert, animated: true, completion: nil)
}
}
}catch let error {
print(error)
}
}
task.resume()
}

Tableview works on sim but not on test device

I have no idea whats wrong with this function. I'm calling it in viewdidload and it prints the array as blank when I load it on my phone. When I do it in the simulator it fills the array though. Using ObjectMapper if that helps at all.
func getData() {
let myURLString = "http://meetup.x10host.com/api/get_event.php?radius=15"
let myURL = NSURL(string: myURLString)!
var myCardsArray = [Card]()
let mySession = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration())
let myDataTask = mySession.dataTaskWithURL(myURL) { (data, response, error) in
guard error == nil else {
let alertController = UIAlertController(title: "No Connection", message:
"Can't connect to database, perhaps turn on your WiFi?", preferredStyle: UIAlertControllerStyle.Alert)
alertController.addAction(UIAlertAction(title: "Okay", style: UIAlertActionStyle.Default,handler: nil))
self.presentViewController(alertController, animated: true, completion: nil)
return
}
do {
let jsonData = try NSJSONSerialization.JSONObjectWithData(data!, options: NSJSONReadingOptions.AllowFragments)
for someCard in jsonData as! NSArray{
let card = Mapper<Card>().map(someCard)
myCardsArray.append(card!)
self.nameArray.append(card!.titlee!)
self.textArray.append(card!.text!)
self.userArray.append(card!.attending!)
self.latArray.append(card!.latitude!)
self.longArray.append(card!.longitude!)
self.timeArray.append(card!.time!)
self.locTextArray.append(card!.locationText!)
dispatch_async(dispatch_get_main_queue()) {
self.tableView.reloadData()
}
}
} catch {
print("There was an error")
}
}
myDataTask.resume()
print(nameArray)
}

How to dismiss a UIAlert with no buttons or interaction in Swift?

I am using a UIAlert to display the string "Loading..." while my iOS application is interacting with a database. Is there any way to pragmatically dismiss it when the action is complete?
code:
let myUrl = NSURL(string: "http://www.test.org/ios.html")
let request = NSMutableURLRequest(URL: myUrl!)
request.HTTPMethod = "POST"
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
let responseString = NSString(data: data!, encoding: NSUTF8StringEncoding)
if error != nil {
print("Error: \(error)")
}
dispatch_async(dispatch_get_main_queue()) {
self.testLabel.text = "\(responseString!)"
// dismiss sendLoading() UIAlert
}
}
}
task.resume()
self.sendLoading()
sendLoading func:
func sendLoading() {
let alertController = UIAlertController(title: "Loading...", message:
"", preferredStyle: UIAlertControllerStyle.Alert)
self.presentViewController(alertController, animated: true, completion: nil)
}
Thank you
Make your alertController as instance variable and when you need to dismiss it just call
self.dismissViewController(alertController, animated:true, completion: nil)
Edit - Adding code.
In your case code would be like -
let alertController : UIAlertController ?
let myUrl = NSURL(string: "http://www.test.org/ios.html")
let request = NSMutableURLRequest(URL: myUrl!)
request.HTTPMethod = "POST"
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
let responseString = NSString(data: data!, encoding: NSUTF8StringEncoding)
if error != nil {
print("Error: \(error)")
}
dispatch_async(dispatch_get_main_queue()) {
self.testLabel.text = "\(responseString!)"
// dismiss sendLoading() UIAlert
self.dismissViewController(alertController!, animated:true, completion: nil)
}
}
}
task.resume()
self.sendLoading()
sendLoading func:
func sendLoading() {
alertController = UIAlertController(title: "Loading...", message:
"", preferredStyle: UIAlertControllerStyle.Alert)
self.presentViewController(alertController, animated: true, completion: nil)
}
The UIAlertController have the function dismissViewControllerAnimated(flag: Bool, completion: (() -> Void)?) that according to Apple:
Dismisses the view controller that was presented modally by the view controller.
Then what you need to do is to keep a reference to the UIAlertController as a property in your UIViewController and then dismiss it as you like, something like this:
// instance of the UIAlertController to dismiss later
var alertController: UIAlertController!
func sendLoading() {
self.alertController = UIAlertController(title: "Loading...", message:
"", preferredStyle: UIAlertControllerStyle.Alert)
self.presentViewController(alertController, animated: true, completion: nil)
}
let myUrl = NSURL(string: "http://www.test.org/ios.html")
let request = NSMutableURLRequest(URL: myUrl!)
request.HTTPMethod = "POST"
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
let responseString = NSString(data: data!, encoding: NSUTF8StringEncoding)
if error != nil {
print("Error: \(error)")
}
dispatch_async(dispatch_get_main_queue()) {
self.testLabel.text = "\(responseString!)"
// dismiss sendLoading() UIAlert
self.alertController.dismissViewControllerAnimated(true, completion: nil)
}
}
}
task.resume()
self.sendLoading()
I hope this help you.