In swift, how do I only perform a segue after a server has given me a certain response? - swift

In swift, how do I only perform a segue after a server has given me a certain response?
I'd like to click a submit button. That will send a request to the server. Then, if the server responds positively, I want a view to be presented modally, otherwise, I want to write a UIAlertView.
I know how to write the UIAlertView and I have the Modal View prepared, I just can't figure out how to write it such that the modal view only presents itself conditionally.

First you need to give a segue identifier on your storyboard. Then you can use:
self.performSegueWithIdentifier("yourSegueIdentifier", sender: nil)

first make a segue between the view controllers and give the segue identifier
eg: self.performSegueWithIdentifier("SegueIdentifier", sender: nil)
and when you get the response of the service request and on the basis of that check the status or the valid http status code.
eg: if status returns true {
self.performSegueWithIdentifier("SegueIdentifier", sender: nil)
}
or if statuscode == 200...299 {
self.performSegueWithIdentifier("SegueIdentifier", sender: nil)
}

Assuming you have your submit button wired up to an IBAction you could do something like this...
#IBAction func submitClicked(sender: UIButton) {
//try to hit server
let serverResponse = server.trySomeFunction()
if serverResponse {
//server response was successful!
self.performSegueWithIdentifier("SegueIdentifier", sender: self)
} else {
//server response failure, present alert
}
}

Related

How to perform the Segue only after authorization with Touch ID

Maybe someone can help, just want to use Segue for Login button to made a transfer to second ViewController only after authorization with Touch ID but application still performing the Segue after user pressing on the button.
import UIKit
import LocalAuthentication
class LoginWindowViewController: UIViewController {
#IBAction func loginButton(_ sender: Any) {
let context: LAContext = LAContext()
if context.canEvaluatePolicy(.deviceOwnerAuthenticationWithBiometrics, error: nil){
context.evaluatePolicy(LAPolicy.deviceOwnerAuthenticationWithBiometrics, localizedReason: "For login you need to use your TouchID", reply: {(wasSuccessful, error) in if wasSuccessful {
DispatchQueue.main.async {
self.shouldPerformSegue(withIdentifier: "LoginComplete", sender: self.navigationController)
}
}else{
print ("Bad TouchID")
}
})
}
}
Thanks
Try implementing this function and checking from where it's being called:
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
}
Remember that as you're calling the segue from your code, if you have already added a segue action from your button to the next ViewController in the storyboard you should remove it and only create a connection between your LoginViewController to the one you want to call
As the documentation states, if you do not override the shouldPerformSegue method, the default implementation returns true for all segues.
The shouldPerformSegue(withIdentifier:sender:) method should determine whether a segue with the specified identifier will or will not be called depending on the return value of your implementation.
Since you already check whether the TouchID authorization was successful, you don't actually need to call shouldPerformSegue, but rather you need to call performSegue.
context.evaluatePolicy(LAPolicy.deviceOwnerAuthenticationWithBiometrics, localizedReason: "For login you need to use your TouchID", reply: {(wasSuccessful, error) in
if wasSuccessful {
DispatchQueue.main.async {
self.performSegue(withIdentifier: "LoginComplete", sender: self.navigationController)
}
}else{
print ("Bad TouchID")
}
})

open other view with segue only works by pressing button

I want to open an other view controller, after checking if it is the first run of the app.
It works when I press a button but not when I call the method openMap
class TutorialController: UIViewController {
override func viewDidLoad() {
//check if the app opens for the first time
if(UserDefaults.standard.bool(forKey: "HasLaunchedOnce"))
{
// app already launched
print("not first launch")
openMap()
}
else
{
// This is the first launch ever
UserDefaults.standard.set(true, forKey: "HasLaunchedOnce")
UserDefaults.standard.synchronize()
print("first launch")
openTutorial()
}
}
func openTutorial(){
}
#IBAction func openMap(){
print("openmap opened")
performSegue(withIdentifier: "openMap", sender: nil)
}
}
I assume, you've connected your button to #IBAction func openMap()
if so, you should not call openMap() action inside your viewDidLoad, but use the same code performSegue(withIdentifier: "openMap", sender: nil) instead in your viewDidAppear:
if(UserDefaults.standard.bool(forKey: "HasLaunchedOnce"))
{
// app already launched
print("not first launch")
performSegue(withIdentifier: "openMap", sender: nil)
}
...
If it doesn't work, you've probably made a mistake with creation of your segue and have connected Button to the destination ViewController directly in your storyboard instead of connecting two controllers:
If so, just remove the old segue, and re-crete it in the way as it is on the image above and assign the same segue id "openMap"
EDITED:
Please, move performing of your segue to the viewDidAppear instead of viewDidLoad, because viewDidLoad is called when the ViewController object is created and it's not yet attached to the window.
Ok, from what I understand is that you want to perform a segue "openMap" when it HasLaunchedOnce. Well what you're doing wrong is that you're calling an #IBAction func. This is my suggestion
if you still want to have that button
create a function and name if whatever you want. Inside this function perform this segue. Link this function to the if else statement and the button.
eg:
//if else statement
if(UserDefaults.standard.bool(forKey: "HasLaunchedOnce"))
{
// app already launched
print("not first launch")
anotherFunction()
}
//#ibaction (scrap this if you don't want the button)
#IBAction func openMap()
{
print("openmap opened")
anotherFunction()
}
//another function
func anotherFunction()
{
performSegue(withIdentifier: "openMap", sender: nil)
}
hope this helps

In swift, how can I wait until a server response is received before I proceed?

I would like to only execute a segue if I get a certain response from the server. In swift, how can I wait until I get a response to continue?
Bottom line, you don't "wait" for the response, but rather simply specify what you want to happen when the response comes in. For example, if you want to perform a segue when some network request is done, you should employ the completion handler pattern.
The issue here is that you're probably accustomed to just hooking your UI control to a segue in Interface Builder. In our case, we don't want to do that, but rather we want to perform the network request, and then have its completion handler invoke the segue programmatically. So, we have to create a segue that can be performed programmatically and then hook your button up to an #IBAction that performs the network request and, if appropriate, performs the segue programmatically. But, note, there should be no segue hooked up to the button directly. We'll do that programmatically.
For example:
Define the segue to be between the two view controllers by control-dragging from the view controller icon in the bar above the first scene to the second scene:
Give that segue a storyboard identifier by selecting the segue and going to the "Attributes Inspector" tab:
Hook up the button (or whatever is going to trigger this segue) to an #IBAction.
Write an #IBAction that performs network request and, upon completion, programmatically invokes that segue:
#IBAction func didTapButton(_ sender: Any) {
let request = URLRequest(...). // prepare request however your app requires
let waitingView = showWaitingView() // present something so that the user knows some network request is in progress
// perform network request
let task = URLSession.shared.dataTask(with: request) { data, response, error in
// regardless of how we exit this, now that request is done, let's
// make sure to remove visual indication that network request was underway
defer {
DispatchQueue.main.async {
waitingView.removeFromSuperview()
}
}
// make sure there wasn't an error; you'll undoubtedly have additional
// criteria to apply here, but this is a start
guard let data = data, error == nil else {
print(error ?? "Unknown error")
return
}
// parse and process the response however is appropriate in your case, e.g., if JSON:
//
// guard let responseObject = try? JSONSerialization.jsonObject(with data) else {
// // handle parsing error here
// return
// }
//
// // do whatever you want with the parsed JSON here
// do something with response
DispatchQueue.main.async {
performSegue(withIdentifier: "SegueToSceneTwo", sender: self)
}
}
task.resume()
}
/// Show some view so user knows network request is underway
///
/// You can do whatever you want here, but I'll blur the view and add `UIActivityIndicatorView`.
private func showWaitingView() -> UIView {
let effectView = UIVisualEffectView(effect: UIBlurEffect(style: .Dark))
effectView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(effectView)
NSLayoutConstraint.activateConstraints([
effectView.leadingAnchor.constraintEqualToAnchor(view.leadingAnchor),
effectView.trailingAnchor.constraintEqualToAnchor(view.trailingAnchor),
effectView.topAnchor.constraintEqualToAnchor(view.topAnchor),
effectView.bottomAnchor.constraintEqualToAnchor(view.bottomAnchor)
])
let spinner = UIActivityIndicatorView(activityIndicatorStyle: .WhiteLarge)
effectView.addSubview(spinner)
spinner.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activateConstraints([
spinner.centerXAnchor.constraintEqualToAnchor(view.centerXAnchor),
spinner.centerYAnchor.constraintEqualToAnchor(view.centerYAnchor)
])
spinner.startAnimating()
return effectView
}

How should i handle Alamofire response in shouldPerformSegueWithIdentifier?

I am trying to learn Swift and what i am trying to do is to create simple login page. Whenever user click on login button, i make a request to my web service in shouldPerformSegueWithIdentifier in order to check if username and password is correct. I dont want to use IBAction (like touchupinside etc). After parsing response that comes from web service, i check if there is any error comes from web service response (like user name is not valid). If there is any error I would like to prevent segue by returning false. But i cant return false inside of Alamofire. So how should i handle it? Here is my code :
override func shouldPerformSegueWithIdentifier(identifier: String?, sender: AnyObject?) -> Bool {
if identifier == "segueLoginToMainPage" {
//...
//Checking if text fields are filled or not
else {
let callURL : String = CONFIG.BASE_URL + "user/login"
let parameters = [HTTPParamNames.FIELD_USER_NAME : self.txtUserName.text,
HTTPParamNames.FIELD_PASSWORD : self.txtPassword.text]
Alamofire.request(.POST, callURL, parameters: parameters)
.responseJSON { (_,_,JSON,_) in
var returnedData = JSON as NSDictionary
if returnedData["status"] as String == "error" {
//thats what i want to do
return false
}
}
}
}
return true
}
But i cant do return false because it is async. I have found some questions like this in stackoverflow but answers are not what exactly i am looking for. Thanks!
You simply should not try to use asynchronous method within shouldPerformSegueWithIdentifier. Instead you should:
Remove segue from login button, itself.
Add segue between the view controllers, themselves. Specifically, control-drag from the originating scene's view controller icon (in Xcode 6+, this icon is in the bar above the scene, in earlier versions of Xcode, this icon is in the bar below the scene) to the destination scene.
Give this new segue a "storyboard identifier".
Hook up login button to an IBAction function.
In that IBAction function, perform your asynchronous network request, and if the login was successful, then programmatically performSegueWithIdentifier, supplying the storyboard identifier that you specified in step 3.
I think that not using IBAction is a good solution in this case but anyway there is a workaround how to achieve your goal. For this workaround you need to declare a variable typed boolean to check wether user is ready to login. Your shouldPerformSegueWithIdentifier method should always return value of that variable but after you make a request. Finally after your asynchronous request finishes call the prepareForSegue method
var readyForLogin = false
override func shouldPerformSegueWithIdentifier(identifier: String?, sender: AnyObject?) -> Bool {
if identifier == "segueLoginToMainPage" {
//...
//Checking if text fields are filled or not
else {
if(!readyForLogin){
let callURL : String = CONFIG.BASE_URL + "user/login"
let parameters = [HTTPParamNames.FIELD_USER_NAME : self.txtUserName.text,
HTTPParamNames.FIELD_PASSWORD : self.txtPassword.text]
Alamofire.request(.POST, callURL, parameters: parameters)
.responseJSON { (_,_,JSON,_) in
var returnedData = JSON as NSDictionary
if returnedData["status"] as String != "error" {
//thats what i want to do
self.readyForLogin = true
self.performSegueWithIdentifier("segueLoginToMainPage", sender: self)
}
}
}}
}
return readyForLogin
}

PerformSegue in StoryBoard

I am working for the first time on storyboard using Swift.
In the first page of the app user will login and when login is done successfully, user will navigate to next page.
But When button is clicked firstly navigation occurs then after web service gets called, but what I want is to authenticate the Login Web service first then navigate with login data to next page.
I have put the identifier on the login button i.e, "login_success" on the button in storyboard and called self.performSegueWithIdentifier, when login is successfull.
Please guide me. Thanks.
You can add segue in the identifier in storyboard. Set the segue in the storyboard from File Owner. And when we get the response from the server, then using,
dispatch_async(dispatch_get_main_queue(), {
if self.loginInfo.userEmail.length > 0{
self.performSegueWithIdentifier("login_success", sender: self)
}
})
Here, I am checking whether response string has value or not.
Then call the method of segue to navigate to next page.
override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!)
{
if segue.identifier == "login_success"
{
var mainVC: MainViewController!
mainVC = segue.destinationViewController as MainViewController
mainVC.mainLoginInfo = loginInfo
}
}
MainViewController is the page to which I want to move after login is done successfully.