Prevent Segue if Empty Text Field

I am having trouble with my app. The problem I am having is that when I leave a text field empty I want it to have a pop-up message that says empty text field and I want it to stop the segue from moving to the next screen. What is happening is it is showing the pop-up message of the empty text field but it still moves to the next screen. This is the code that I currently have:


   let firstNameAlert : UIAlertView = UIAlertView(title: "Blank Text Field", message: "Please fill in first name.",       delegate: nil, cancelButtonTitle: "OK")
        let lastNameAlert : UIAlertView = UIAlertView(title: "Blank Text Field", message: "Please fill in last name.",       delegate: nil, cancelButtonTitle: "OK")
        let emailAlert : UIAlertView = UIAlertView(title: "Blank Text Field", message: "Please fill in email.",       delegate: nil, cancelButtonTitle: "OK")
        let addressAlert : UIAlertView = UIAlertView(title: "Blank Text Field", message: "Please fill in address.",       delegate: nil, cancelButtonTitle: "OK")
        let phoneFromDefaults = defaults.object(forKey: "Phone Number") as? String
        
        if firstName.text == ""{
            firstNameAlert.show()
        }else if lastName.text == ""{
            lastNameAlert.show()
        }else if email.text == ""{
            emailAlert.show()
        }else if addressLine1.text == ""{
            addressAlert.show()
        }else{
            performSegue(withIdentifier: "Create Account", sender: AnyObject.self)
        }

Accepted Reply

I was able to fix the issue by setting the segue back to push.

Replies

Please show more context. Have you placed the segue from the source view controller? Isn't it from any of UI controls or cells?

Also you should better show where in your code (which class? which method?) the lines you have shown exist.

Are you sure it is the

performSegue(withIdentifier: "Create Account", sender: AnyObject.self)


which is executd.


Add a print just before to check:


} else {
     print("Will performSegue")
     performSegue(withIdentifier: "Create Account", sender: AnyObject.self) 
}

What Claude31 said and...

....if it does print "Will performSegue" then add a similar print statement before the Alert.show(s) to be sure that is the line that is showing the Alert.


As written your code will do what you want it to. That usually means 'the problem in the code is never where you are looking'.

I added the will perform segue print. However, when I test it out it does not print yet it still goes to the next screen and shows an error message for the blank text field.

So you have another call to segue.

=> Search everywhere for "segue" in your code


Or may be you defined in IB ?

=> Inspect in IB all the arrows going to the next screen

func shouldPerformSegue(withIdentifier identifier: String, sender: Any?) -> Bool
    guard let firstName = productNameTextField.text, !firstName.isEmpty, firstName.count > 0  else {
        firstNameAlert.show()
        return false
    }
    guard let lastName = productNameTextField.text, !lastName.isEmpty, lastName.count > 0  else {
        lastNameAlert.show()
        return false
    }

    guard let email = productNameTextField.text, !email.isEmpty, email.count > 0  else {
        emailAlert.show()
        return false
    }

    guard let addressLine1 = productNameTextField.text, !addressLine1.isEmpty, addressLine1.count > 0  else {
        addressAlert.show()
        return false
    }
    return true
}

I was able to find what was causing it to go to the next screen I had the segue set to show I changed it to custom. What is happening now is when I press the create account button but leave the text fields blank the app just crashes and doesn't show any pop up message.

Please clarify.


Have you defined the segue in IB ? (seems so)

Have you a segue AND an IBAction defined for the button ?

==> You should not. You cannot have both.

So either

- define the segue from yje viewController (not starting from the button) and keep the IBAction as it is

- remove the IBAction and put the code for alerting in shouldPerformSegue ; return false in case of alert.


And return to show segue

What I did was I control-dragged from a button in one view controller to another then I selected the custom segue. I also for that button made an action and in that action, I put the code that I had showed earlier when I first had asked the question.

control-dragged from a button in one view controller to another


That is one thing you should not do when you want to manage the segue programmatically with `performSegue(withIdentifier:sender:)`.


- Delete the existing segue

- Add new segue control-dragging from the source view controller to another

(Not from a button!)

- Set the identifer of the new segue as the old one "Create Account"

You should not have both

- a segue like this in IB from button to VC and

- an IBAction.


As I explained before:

So either

- define the segue from the viewController (not starting from the button) and keep the IBAction as it is

- remove the IBAction and put the code for alerting in shouldPerformSegue ; return false in case of alert.

I did as you said by setting the segue from the view controller and keeping the code in the IBAction. But what is happening now is that it shows the pop-up message if there is a blank text field but once all text field is filled in and the user presses the create account button the app crashes.

Please, post the complete code for the controller.


Have you performed a Clean Build Folder after resetting the segues ?


How is segue precisely defined now ? Standard Show or Custom ?

What is the exact crash message ?

Check segue identifier, must be exactly the same as in code (including uppercase).

If not matching, you'll get a crash with an error such as (here, account vs Account):

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Receiver (<ViewController: 0x7f8bc3d0fff0>) has no segue with identifier 'Create account''


Another point: change the sender to self for instance, or sender (the IBAction sender)

performSegue(withIdentifier: "Create Account", sender: self)


I would need to look at the rest of controller code to check if that could be the cause of crash

Did you solve your issue ?

Sorry, I haven't responded sooner I have just been very busy lately. This is the complete code:


import UIKit
import FirebaseUI
import FirebaseAuth
import FirebaseDatabase

class signInViewController: UIViewController{
    //MARK: Properties
    let defaults = UserDefaults.standard
    let verificationID = UserDefaults.standard.string(forKey: "authVerificationID")
    
    @IBOutlet weak var phoneNumber: UITextField!
    @IBOutlet weak var firstName: UITextField!
    @IBOutlet weak var lastName: UITextField!
    @IBOutlet weak var email: UITextField!
    @IBOutlet weak var addressLine1: UITextField!
    @IBOutlet weak var addressLine2: UITextField!
    @IBOutlet weak var verificationCode: UITextField!
    @IBOutlet weak var phoneNumberContinueButton: CustomButton!
  
    
    //MARK: Sign In With Email
    func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool
    {
        if (textField == phoneNumber) {
            let newString = (textField.text! as NSString).replacingCharacters(in: range, with: string)
            let components = newString.components(separatedBy: NSCharacterSet.decimalDigits.inverted)
            let decimalString = components.joined(separator: "") as NSString
            let length = decimalString.length
            let hasLeadingOne = length > 0 && decimalString.character(at: 0) == (1 as unichar)
            
            if length == 0 || (length > 10 && !hasLeadingOne) || length > 11 {
                let newLength = (textField.text! as NSString).length + (string as NSString).length - range.length as Int
                
                return (newLength > 10) ? false : true
            }
            var index = 0 as Int
            let formattedString = NSMutableString()
            
            if hasLeadingOne {
                formattedString.append("1 ")
                index += 1
            }
            if (length - index) > 3 {
                let areaCode = decimalString.substring(with: NSMakeRange(index, 3))
                formattedString.appendFormat("(%@)", areaCode)
                index += 3
            }
            if length - index > 3 {
                let prefix = decimalString.substring(with: NSMakeRange(index, 3))
                formattedString.appendFormat("%@-", prefix)
                index += 3
            }
            
            let remainder = decimalString.substring(from: index)
            formattedString.append(remainder)
            textField.text = formattedString as String
            return false
        }
        else {
            return true
        }
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    @IBAction func passwordAuth(_ sender: Any) {
        let authUI = FUIAuth.defaultAuthUI()
        
        guard authUI != nil else{
            //Log the error
            return
        }
        
        authUI?.delegate = self
        
        let authViewController = authUI!.authViewController()
        
        present(authViewController, animated: true, completion: nil)
    }
    
    //MARK: Phone Auth
    @IBAction func sendPhoneAuthCode(_ sender: Any){
        if let phoneNum = phoneNumber.text {    // No more need to unwrap phoneNum later
            
            
            PhoneAuthProvider.provider().verifyPhoneNumber(phoneNum, uiDelegate: nil) { (verificationID, error) in
                
                // Sign in using the verificationID and the code sent to the user
                // …
                //  if phoneNum == nil {
                if error != nil {
                    print("verifyPhoneNumber went NOT OK")
                    // REPLACE BY print       self.present(alert, animated: true, completion: nil)
                    
                    
                    DispatchQueue.global(qos: .userInitiated).async {
                        let titre = " Missing Information "
                        let detailMsg = "Please enter your phone number"
                        let controller = UIAlertController(title: titre, message: detailMsg, preferredStyle: .alert)
                        
                        let okAction = UIAlertAction(title: "OK", style: UIAlertAction.Style.cancel)
                        controller.addAction(okAction)
                        DispatchQueue.main.async {
                            self.present(controller, animated: true, completion: nil)
                        }
                        
                    }
                    
                    
                    return
                } else {
                    print("verifyPhoneNumber went OK")
                    // You have to test credential here, where do you do this ?
                }
            }
        } else {
            print("phoneNumber.text is nil")
            return
        }
    }

    
    @IBAction func enterVerificationCode(_ sender: Any){
        let credential = PhoneAuthProvider.provider().credential(
            withVerificationID: verificationID!,
            verificationCode: verificationCode.text!)
        
        Auth.auth().signInAndRetrieveData(with: credential) { (authResult, error) in
            if error != nil {
                // ...
                return
            }
            // User is signed in
            // ...
        }
    }
    
    @IBAction func createAccount(_ sender: Any) {
        let firstNameAlert : UIAlertView = UIAlertView(title: "Blank Text Field", message: "Please fill in first name.",       delegate: nil, cancelButtonTitle: "OK")
        let lastNameAlert : UIAlertView = UIAlertView(title: "Blank Text Field", message: "Please fill in last name.",       delegate: nil, cancelButtonTitle: "OK")
        let emailAlert : UIAlertView = UIAlertView(title: "Blank Text Field", message: "Please fill in email.",       delegate: nil, cancelButtonTitle: "OK")
        let addressAlert : UIAlertView = UIAlertView(title: "Blank Text Field", message: "Please fill in address.",       delegate: nil, cancelButtonTitle: "OK")
        
        if firstName.text == ""{
            firstNameAlert.show()
        }else if lastName.text == ""{
            lastNameAlert.show()
        }else if email.text == ""{
            emailAlert.show()
        }else if addressLine1.text == ""{
            addressAlert.show()
        }else{
            print("Will performSegue")
            performSegue(withIdentifier: "Create Account", sender: self)
        }
    }
}

extension signInViewController: FUIAuthDelegate{
    func authUI(_ authUI: FUIAuth, didSignInWith authDataResult: AuthDataResult?, error: Error?) {
        //Check if there was an error
        if error != nil{
            //Log the error
            return
        }
        
        authDataResult?.user.uid//Use for associating information in Firebase Database
        
        performSegue(withIdentifier: "passwordAuthLoggedIn", sender: self)
    }
}



I did do a clean build.


In response to if I have the correct name for the segue this was the error that I got:



Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Could not perform segue with identifier 'Create Account'. A segue must either have a performHandler or it must override -perform.'

*** First throw call stack:

(

0 CoreFoundation 0x00000001041aa6fb __exceptionPreprocess + 331

1 libobjc.A.dylib 0x000000010374eac5 objc_exception_throw + 48

2 UIKitCore 0x0000000107b70392 -[UIStoryboardSegue _prepare] + 0

3 UIKitCore 0x0000000107b70b9f -[UIStoryboardSegueTemplate _performWithDestinationViewController:sender:] + 276

4 UIKitCore 0x0000000107b70a5d -[UIStoryboardSegueTemplate _perform:] + 82

5 UIKitCore 0x00000001073d854b -[UIViewController performSegueWithIdentifier:sender:] + 99

6 UIKit 0x0000000131efaec1 -[UIViewControllerAccessibility performSegueWithIdentifier:sender:] + 102

7 Drive Day 0x00000001010e0fe9 $s7Drive_Day20signInViewControllerC13createAccountyyypF + 5001

8 Drive Day 0x00000001010e11fc $s7Drive_Day20signInViewControllerC13createAccountyyypFTo + 76

9 UIKitCore 0x00000001079d9204 -[UIApplication sendAction:to:from:forEvent:] + 83

10 UIKitCore 0x000000010742ec19 -[UIControl sendAction:to:forEvent:] + 67

11 UIKitCore 0x000000010742ef36 -[UIControl _sendActionsForEvents:withEvent:] + 450

12 UIKitCore 0x000000010742deec -[UIControl touchesEnded:withEvent:] + 583

13 UIKitCore 0x0000000107a11eee -[UIWindow _sendTouchesForEvent:] + 2547

14 UIKitCore 0x0000000107a135d2 -[UIWindow sendEvent:] + 4079

15 UIKitCore 0x00000001079f1d16 -[UIApplication sendEvent:] + 356

16 UIKit 0x0000000131ea84af -[UIApplicationAccessibility sendEvent:] + 85

17 UIKitCore 0x0000000107ac2293 __dispatchPreprocessedEventFromEventQueue + 3232

18 UIKitCore 0x0000000107ac4bb9 __handleEventQueueInternal + 5911

19 CoreFoundation 0x0000000104111be1 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17

20 CoreFoundation 0x0000000104111463 __CFRunLoopDoSources0 + 243

21 CoreFoundation 0x000000010410bb1f __CFRunLoopRun + 1231

22 CoreFoundation 0x000000010410b302 CFRunLoopRunSpecific + 626

23 GraphicsServices 0x000000010c6cc2fe GSEventRunModal + 65

24 UIKitCore 0x00000001079d7ba2 UIApplicationMain + 140

25 Drive Day 0x00000001010fef5b main + 75

26 libdyld.dylib 0x00000001060b1541 start + 1

27 ??? 0x0000000000000001 0x0 + 1

)

libc++abi.dylib: terminating with uncaught exception of type NSException