2

I have a view controller called LoginViewController that conforms to UITextFieldDelegate and have two text field objects namely Email and Password. There's an email validation function imposed on the Email text field and should the validation fails, an instance of UIAlertController will kick in.

Here's the screenshots of the scenes and the code before I continue with my question:

Log In Scene

Log In Scene with Error

ps: There are lots of print statement to allow me to understand the flow of execution

func textFieldShouldEndEditing(textField: UITextField) -> Bool {
    guard self.validateEmail(self.emailTextField.text!) else {
        print("self.presentedViewController: \(self.presentedViewController)")
        if self.presentedViewController == nil {
            let alertController = UIAlertController(title: "Invalid Email Address", message: "Please try again.", preferredStyle: UIAlertControllerStyle.Alert)
            alertController.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil))
            self.presentViewController(alertController, animated: true, completion: nil)
        }
        print("textFieldShouldEndEditing returnin false: \(textField.placeholder!) text field w/ value: \(textField.text!)")
        return false
    }
    print("textFieldShouldEndEditing returning true: \(textField.placeholder!) text field w/ value: \(textField.text!)")
    return true
}

func textFieldShouldBeginEditing(textField: UITextField) -> Bool {
    print("textFieldShouldBeginEditing: \(textField.placeholder!) text view")
    return true
}

func textFieldDidEndEditing(textField: UITextField) {
    print("textFieldDidEndEditing: \(textField.placeholder!) text field with value: \(textField.text!)")
}

func textFieldDidBeginEditing(textField: UITextField) {
    print("textFieldDidBeginEditing: \(textField.placeholder!) text field")
}

func textFieldShouldReturn(textField: UITextField) -> Bool {
    if !(self.emailTextField.text?.isEmpty)! && !(self.passwordTextField.text?.isEmpty)! {
        self.emailTextField.resignFirstResponder()
        self.passwordTextField.resignFirstResponder()
    }
    else if !(self.emailTextField.text?.isEmpty)! {
        self.passwordTextField.becomeFirstResponder()
    }
    print("textFieldShouldReturn: \(textField.placeholder!) text field w/ value: \(textField.text!)")
    return true
}

When properly formatted email address is entered in the Email text field and focus is moved to the next text field (Password) either via the next key or manually tapping on the next text field (Password), the following chains of delegate methods occurs as expected (and understood):

console output:

Format: [name of delegate method: text field placeholder value (+ text value)]

 textFieldShouldBeginEditing: Email text field
 textFieldDidBeginEditing: Email text field
 textFieldShouldBeginEditing: Password text field
 textFieldShouldEndEditing returning true: Email text field w/ value: abc@abc.co
 textFieldDidEndEditing: Email text field w/ value: abc@abc.co
 textFieldDidBeginEditing: Password text field
 email field is filled
 textFieldShouldReturn: Email text field w/ value: abc@abc.co

The issue arises when incorrect formatted email address is detected on the Email text field. The UITextFieldDelegate method textFieldShouldEndEditing is invoked three times instead of once as instance of UIAlertController is presented.

ps: the alert view is only however displayed once on the screen.

console output: Format: [name of delegate method: text field placeholder value (+ text value)]

 textFieldShouldBeginEditing: Email text field
 textFieldDidBeginEditing: Email text field
 textFieldShouldBeginEditing: Password text field

 //1st occurence
 self.presentedViewController: nil
 textFieldShouldEndEditing returnin false: Email text field w/ value: bob

 //2nd occurence
 self.presentedViewController: Optional(<UIAlertController: 0x7f9018662df0>)
 textFieldShouldEndEditing returnin false: Email text field w/ value: bob

 //3rd occurence
 self.presentedViewController: Optional(<UIAlertController: 0x7f9018662df0>)
 textFieldShouldEndEditing returnin false: Email text field w/ value: bob

Based on the explanation from this so's thread: "textFieldShouldEndEditing called multiple times", my understanding is that as focus is about to leave the Email text field (the 1st occurence of textFieldShouldEndEditing method kicked in), focus is already on the Password text field thus gaining the first responder status. But, because invalid email is detected, an alert controller is presented and Password text field abruptly loses focus and thus triggers textFieldShouldEndEditing too. So, I am expecting the textFieldShouldEndEditing to only get triggered twice (one due to Email text field. The other due to Password text field) instead of three times (all due to Email text field) like this:

 textFieldShouldBeginEditing: Email text field
 textFieldDidBeginEditing: Email text field
 textFieldShouldBeginEditing: Password text field

 self.presentedViewController: nil
 textFieldShouldEndEditing returnin false: Email text field w/ value: bob

 self.presentedViewController: Optional(<UIAlertController: 0x7f9018662df0>)
 textFieldShouldEndEditing returnin false: Password text field w/ value: nil

But, that's not the case. Can anyone enlighten why my understanding is not correct. Thanks for any pointer provided.

Community
  • 1
  • 1
yohannes
  • 1,022
  • 11
  • 13

1 Answers1

1

Place your email validation code in

func textFieldDidEndEditing(textField: UITextField)

instead of

func textFieldShouldEndEditing(textField: UITextField) -> Bool

and before showing alert controller you can also add (if you want to)

self.view.endEditing(true)

and on tap OK action you can set

self.emailTextField.becomeFirstResponder()

Hope this approach will solve your problem.

Haripal Wagh
  • 572
  • 1
  • 5
  • 18
  • Hi Haripal, I have tried your suggestion but it produces the following error: `Warning: Attempt to present on which is already presenting (null)`. The only way (that I know) to alleviate this error is to check whether `presentedViewController` is nil. If it's nil, then create the alert controller & action then present it. Then, the error is gone. So I am back to square one. – yohannes Aug 10 '16 at 05:11
  • I have used same approach to achieve what you are trying to achieve... Use below code to present alert controller dispatch_async(dispatch_get_main_queue()) { self.presentViewController(alertController, animated: true, completion: nil) } – Haripal Wagh Aug 11 '16 at 07:27