ShouldPerformSegue Fires Twice - ios

I have currently come across a weird problem. I have a storyboard which has viewController A with a button. From that button, I have created a segue in storyboard to viewController B. When the button is clicked, a segue is fired.
The button is a Login button, so I need to validate the login details before the segue is performed.
In ViewController A, when the button is pressed, I have following code:
#IBAction func SignInButtonPressed(_ sender: Any) {
guard let email = username.text , username.text != "" else {
return self.successLogin = false
}
guard let pass = password.text , password.text != "" else {
return self.successLogin = false
}
AuthService.instance.loginUser(email: email, password: pass) { (success) in
if success {
self.successLogin = true
} else {
self.displayAlertView(title: USER_LOGIN_FAILED_TITLE, message: USER_LOGIN_FAILED_MESSAGE)
}
}
}
override func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool {
if successLogin != true && identifier == "homeSegue" {
self.displayAlertView(title: USER_LOGIN_FAILED_TITLE, message: USER_LOGIN_FAILED_MESSAGE)
return false
} else {
return true
}
}
But with this, the first click shows the alert in shouldPerformSegue, and then a second click allows the login.
Any suggestions would be helpful on how can I fix this.
Thanks

You should perform the segue inside your AuthService success closure callback, you should also remove the unwind segue from the button, the button must only execute the login logic, if the login went well then the segue must be performed but not before
AuthService.instance.loginUser(email: email, password: pass) { (success) in
if success {
self.successLogin = true
self.performSegue(withIdentifier: "homeSegue", sender: self)
} else {
self.displayAlertView(title: USER_LOGIN_FAILED_TITLE, message: USER_LOGIN_FAILED_MESSAGE)
}
}
should work now

You can run segue programmatically.
performSegue(withIdentifier: "homeSegue", sender: self)
If you need to perform custom logic, moreover postpone navigation, it is better to bind button to the action, and trigger segue you need from it.

Related

How do I stop a segue from going through programmatically in Swift?

Here is the code:
#IBAction func loginTapped(_ sender: Any) {
let email = emailTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
let password = passwordTextField.text!.trimmingCharacters(in: .whitespacesAndNewlines)
Auth.auth().signIn(withEmail: email, password: password) { (result, error) in
if error != nil {
self.errorLabel.text = error!.localizedDescription
self.errorLabel.alpha = 1
print(error!.localizedDescription)
}
else {
self.performSegue(withIdentifier: "loginSegue", sender: nil)
print("User is signed in with Firebase.")
}
}
}
I have a segue, loginSegue, connected from the login button to the homeViewController. Within in the if error statement I would like to stop the segue from going through because the user has not signed in. The goal here is not allow the user to go forward if they get an error. Is there an "opposite" to the performSegue(withIdentifier: String, sender: Any?) ?
First , there is no "opposite" to performSegue(withIdentifier: String, sender: Any?).
But the issue is not about this. I think you wired the segue from the login button and gave it an identifier. If you wire a segue from a button directly the button is always going to execute that segue. Doing some operations in the button's action does not effect.
You need to wire a segue from FirstVc to SecondVc (not from the button) and then give the segue an identifier. Then, from the button's action you can check if there is no error and call performSegue(withIdentifier: String, sender:) passing your segue's identifier.
I think your button is connected to perform segue in storyboard. So your button has two actions - one from storyboard to perform segue and second in your code. Just remove the connection from storyboard and connect only UIViewControllers not with your button.
You could override the shouldPerformSegue(withIdentifier:,sender:) method and return false if the login fails and you don't want to perform the segue. Here's an example
override func shouldPerformSegue(withIdentifier identifier: String?, sender: Any?) -> Bool {
if let ident = identifier {
if ident == "YourIdentifier" {
if loginSuccess != true {
return false
}
}
}
return true
}

Performing Segue with Prepare after validation check

I have a form that needs to be validated before performing a segue and also sending over the data to the next view controller. For now I'm just checking to see if all text fields are filled in:
#IBAction func startBtn(_ sender: Any) {
if(idInput.text == "" || dob1Field.text == "" || dob2Field.text == "" || dob3Field.text == ""){
print("no text")
}
}
My idea is, when the start button is pressed, check if all fields are filled, if they are use prepare to segue to the next VC and send the data.
I'm struggling to understand how to do this, I linked the start button on the storyboard to the VC and gave it an identifier mainUse since it is going to the mainUseController
Here is the prepare function:
override func prepare(for segue: UIStoryboardSegue, sender: Any?){
if segue.identifier == "mainUse"{
let vc = segue.destination as! mainUseController
}
}
The part I'm struggling to understand is how to call the prepare function once the check is done and succeeded. Thanks.
I linked the start button on the storyboard to the VC and gave it an
identifier mainUse since it is going to the mainUseController
Well here is the issue: your segue seems to be generated by dragging from the button to the destination view controller, don't do this because the segue will performed regardless of what's implemented in the button action. Instead drag from the view controller itself (but not from the button) to the destination view controller:
control + drag from the view controller to the destination controller
At this point, tapping the button won't forcibly navigates you to the destination view controller. Next what you should do is to perform the segue if the conditions are met, by calling performSegue(withIdentifier:sender:) method:
#IBAction func startBtn(_ sender: Any) {
if idInput.text == "" || dob1Field.text == "" || dob2Field.text == "" || dob3Field.text == "" {
print("no text")
return
}
// just don't forget to assign the segue identifier as 'mainUse'...
performSegue(withIdentifier: "mainUse", sender: nil)
}
Use the following code to perform the segue, first check if all condition fulfill then fire segue else display error message accordingly.
#IBAction func startBtn(_ sender: Any) {
if(idInput.text == "" || dob1Field.text == "" || dob2Field.text == "" || dob3Field.text == ""){
print("no text")
//Show alert message here
}else{
self.performSegue(withIdentifier: "mainUse", sender: self)
}
}
prepare(for segue:) method is called by the ViewController delegate, you should avoid putting code there that you need to trigger.
What you can call for segueing is performSegue(withIdentifier:sender:)
More in:
https://developer.apple.com/documentation/uikit/uiviewcontroller/1621413-performsegue
IMPORTANT CAVEAT
Remember that your outlets are nil when segueing, if you want to assign or pass data to the receiver VC, create a strong property and assign that value before you segue but after the VC instantiation. Labels, texts, etc won't receive any data until they're draw.
If you need the entries from the text fields to be sent over to the next view controller, create a placeholder property and assign it during the segueing process.
You have a nice day!
#IBAction func startBtn(_ sender: Any) {
if idInput.text?.isEmpty ?? true || dob1Field.text?.isEmpty ?? true || dob2Field.text?.isEmpty ?? true || dob3Fieldtext?.isEmpty ?? true { print("some textField is empty") return }
dispatch_async(dispatch_get_main_queue()) { [unowned self] in
self.performSegueWithIdentifier("YourIdentifier", sender: self)
}
}
If you are using navigation controller:
#IBAction func startBtn(_ sender: Any) {
if idInput.text?.isEmpty ?? true || dob1Field.text?.isEmpty ?? true || dob2Field.text?.isEmpty ?? true || dob3Fieldtext?.isEmpty ?? true { print("some textField is empty") return }
if let viewController = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "YourIdentifier") as? NextViewController{
if let navigator = navigationController {
navigator.pushViewController(viewController, animated: true)
}
}
}

Swift Firebase login

I'm having a problem where every time I enter the right credentials, it brings me to one view controller then opens up the same view controller again even though I only have the login viewer controller linked to one view controller. If I don't enter the right credentials it still brings me into the linked view controller. Here is the code.
EDIT: Using a push segue(show)
#IBAction func loginTapped(_ sender: Any) {
if let Email = userEmail.text, let Pass = userPassword.text{
Auth.auth().signIn(withEmail: Email, password: Pass, completion: { (user, error) in
if error != nil{
print("incorrect")
}
else{
if error == nil{
self.performSegue(withIdentifier: "loginPage", sender: self)
print("correct")
}
}
})
}
}
I don't know if you've fixed your problem, but check your storyboard. Sounds like you have a segue connected from the button to the next ViewController which would result in pressing the button and it'll always push that ViewController.
To do this easily just see if you have a segue connected from the button to your destination ViewController in your MainStoryboard.

Prevent Segue Transition in Login Swift/Parse

I have a login view controller where it should prevent the user from transition to the next view controller via a segue called "toMasterTab". I think the logic might be wrong - if the user entered the correct credentials and is not empty, it transitions fine, but if the user entered no credentials (nil) and entered the wrong credentials, then it should prevent the segue. So far, I can only get the UIAlertView to pop up, but other than that, I can't solve this...
#IBAction func loginButton(sender: AnyObject) {
let RedPlanetUser = RPUsername.text
let RedPlanetUserPassword = RPUserPassword.text
PFUser.logInWithUsernameInBackground(RedPlanetUser!, password: RedPlanetUserPassword!) {
(user: PFUser?, error: NSError?) -> Void in
if user != nil {
// Do stuff after successful login
self.performSegueWithIdentifier("toMasterTab", sender: self)
print("User logged in successfully")
} else {
// Login failed
print("User log in failed")
// Present alert
var alert = UIAlertView(title: "Login Failed",
message: "The username and password do not match.",
delegate: self,
cancelButtonTitle: "Try Again")
alert.show()
func shouldPerformSegueWithIdentifier(identifier: String!, object: AnyObject) -> Bool {
let identifier = "toMasterTab"
// prevent segue
return false
}
}
}
}
I believe you should be overriding the
override func shouldPerformSegueWithIdentifier
The problem was that the segue was connected to the button, so it automatically performed the segue even when the conditions were NOT met. I connected the segue from VC1 to VC2 and used the following code when the conditions were met, and didn't call the segue when the conditions were erroneous:
self.performSegueWithIdentifier("toMasterTab", sender: self)

ios swift: #IBAction handling to avoid segue

I have 2 views, first to enter some data and second to fill in more details based on the data the user entered on the first view.
Therfore I have a function like:
#IBAction func addNewCardSet(sender: AnyObject) { ...
let givenCardSetName = newCardSetName.text
if givenCardSetName != "" {
... save routine ...
}else{
updateLabel("Please fill in a name")
}
I also added a segue to the addNewCardSet Button to do a segue to the second view. What happens now is that if the user doesn't enter a name, I can see the message label saying "Please fill in the name" but one little moment later the segue takes place and send the user to the next view without any saved data...
What can I do to "allow" the segue only, if my save method took place with no errors and it is the time to do the segue?
You can implement shouldPerformSegueWithIdentifier method:
override func shouldPerformSegueWithIdentifier(identifier: String?, sender: AnyObject?) -> Bool {
if let ident = identifier {
if ident == "YOUR IDENTIFIER HERE" {
let givenCardSetName = newCardSetName.text
if givenCardSetName != "" {
return true
}else{
return false
}
}
}
return true
}

Resources