22

Can anyone tell me how to validate UITextFields inside of a UIAlertController?

I need it to prevent the user from clicking "Save" unless both fields are entered.

Here is my code so far:

@IBAction func btnStart(sender: AnyObject) {
    var alert = UIAlertController(title: "New user",
        message: "Add a new user",
        preferredStyle: .Alert)

    let saveAction = UIAlertAction(title: "Save",
        style: .Default) { (action: UIAlertAction!) -> Void in

            self.textFieldName = alert.textFields![0] as UITextField
            self.textFieldEmail = alert.textFields![1] as UITextField
            self.saveUser(self.textFieldName.text, email: self.textFieldEmail.text)
            self.tableView.reloadData()
    }

    saveAction.enabled = false

    let cancelAction = UIAlertAction(title: "Cancel",
        style: .Default) { (action: UIAlertAction!) -> Void in
    }

    alert.addTextFieldWithConfigurationHandler {
        (textFieldName: UITextField!) in
        textFieldName.placeholder = "Enter full name"
    }

    alert.addTextFieldWithConfigurationHandler {
        (textFieldEmail: UITextField!) in
        textFieldEmail.placeholder = "Enter valid email adress"
        textFieldEmail.keyboardType = .EmailAddress

    }
    alert.addAction(saveAction)
    alert.addAction(cancelAction)

    presentViewController(alert,
        animated: true,
        completion: nil)
}

This is my function for validating the Email field:

func isValidEmail(testStr:String) -> Bool {
    let emailRegEx = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}"

    if let emailTest = NSPredicate(format:"SELF MATCHES %@", emailRegEx) {
        return emailTest.evaluateWithObject(testStr)
    }
    return false
}
Brian
  • 14,610
  • 7
  • 35
  • 43
MarkoDeveloper
  • 287
  • 1
  • 3
  • 11
  • possible duplicate of [Check on UIAlertController TextField for enabling the button](http://stackoverflow.com/questions/24474762/check-on-uialertcontroller-textfield-for-enabling-the-button) – Duyen-Hoa Jun 02 '15 at 13:13

10 Answers10

34

This can be done by extending UIAlertViewController:

extension UIAlertController {

    func isValidEmail(_ email: String) -> Bool {
        return email.characters.count > 0 && NSPredicate(format: "self matches %@", "[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,64}").evaluate(with: email)
    }

    func isValidPassword(_ password: String) -> Bool {
        return password.characters.count > 4 && password.rangeOfCharacter(from: .whitespacesAndNewlines) == nil
    }

    func textDidChangeInLoginAlert() {
        if let email = textFields?[0].text,
            let password = textFields?[1].text,
            let action = actions.last {
            action.isEnabled = isValidEmail(email) && isValidPassword(password)
        }
    }
}

// ViewController
override func viewDidLoad() {
    super.viewDidLoad()

    let alert = UIAlertController(title: "Please Log In", message: nil, preferredStyle: .alert)

    alert.addTextField {
        $0.placeholder = "Email"
        $0.addTarget(alert, action: #selector(alert.textDidChangeInLoginAlert), for: .editingChanged)
    }

    alert.addTextField {
        $0.placeholder = "Password"
        $0.isSecureTextEntry = true
        $0.addTarget(alert, action: #selector(alert. textDidChangeInLoginAlert), for: .editingChanged)
    }

    alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))

    let loginAction = UIAlertAction(title: "Submit", style: .default) { [unowned self] _ in
        guard let email = alert.textFields?[0].text,
            let password = alert.textFields?[1].text
            else { return } // Should never happen

        // Perform login action
    }

    loginAction.isEnabled = false
    alert.addAction(loginAction)
    present(alert, animated: true)
}

enter image description here

Scott Gardner
  • 8,603
  • 1
  • 44
  • 36
  • 1
    Great answer, the only thing that needs changing is adding @objc before the `textDidChangeInLoginAlert()` function – Swinny89 Jun 13 '19 at 13:56
31

Most elegant way is to use

NotificationCenter.default.addObserver(forName: NSNotification.Name.UITextFieldTextDidChange...

Swift 3.0 example

let alert = UIAlertController(title: nil, message: nil, preferredStyle: .alert)
    alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
    let saveAction = UIAlertAction(title:"Save", style: .destructive, handler: { (action) -> Void in

    })
    alert.addAction(saveAction)
    alert.addTextField(configurationHandler: { (textField) in
        textField.placeholder = "Enter something"
        NotificationCenter.default.addObserver(forName: NSNotification.Name.UITextFieldTextDidChange, object: textField, queue: OperationQueue.main) { (notification) in
            saveAction.isEnabled = textField.text!.length > 0
        }
    })
    present(alert, animated: true, completion: nil)
imike
  • 5,515
  • 2
  • 37
  • 44
  • 3
    Don't you need to remove the observer at some point, or will the textfield dealloc normally despite having an observer? – BadPirate Apr 03 '17 at 17:48
  • If your app targets iOS 9.0 and later or macOS 10.11 and later, you don't need to unregister an observer in its dealloc method. (yay!) See docs here: https://developer.apple.com/documentation/foundation/notificationcenter/1413994-removeobserver – Yohst Apr 25 '18 at 04:01
9

Swift 4.0 Example

This is based on Mihael Isaev's answer. I had to change it up a bit to get the Save button to NOT be active immediately. I tried with and without the placeholder text. In the end, had to specifically inactivate Save to start with. In my case, I elected to use an alert title rather than placeholder text. But, it worked the same either way.

let alert = UIAlertController(title: "Enter Username", message: nil, preferredStyle: .alert)

alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { (action) -> Void in}))
let saveAction = UIAlertAction(title:"Save", style: .destructive, handler: { (action) -> Void in
})
alert.addAction(saveAction)
alert.addTextField(configurationHandler: { (textField) in
    textField.text = ""
    saveAction.isEnabled = false
    NotificationCenter.default.addObserver(forName: NSNotification.Name.UITextFieldTextDidChange, object: textField, queue: OperationQueue.main) { (notification) in
        saveAction.isEnabled = textField.text!.length > 0
    }
})
self.present(alert, animated: true, completion: nil)
Todd G
  • 103
  • 1
  • 4
  • For Swift 5 just drop the `NSNotification.Name.` from the `forName` parameter. I also recommend passing both `textField` and `saveAction` as weak to prevent a cycle – Allison Jul 03 '19 at 23:11
6

For Swift 4.2 (NSNotification.Name.UITextFieldTextDidChange) update:

let alert = UIAlertController(title: nil, message: nil, preferredStyle: .alert)
    alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
    let saveAction = UIAlertAction(title:"Save", style: .destructive, handler: { (action) -> Void in

    })
    alert.addAction(saveAction)
    alert.addTextField(configurationHandler: { (textField) in
        textField.placeholder = "Enter something"
        NotificationCenter.default.addObserver(forName: UITextField.textDidChangeNotification, object: textField, queue: OperationQueue.main) { (notification) in
            saveAction.isEnabled = textField.text?.count > 0
        }
    })
    present(alert, animated: true, completion: nil)
Franco Rojas
  • 91
  • 2
  • 4
3

This can be achieved via NSNotificationCenter before you display the alert controller, all you have to do is ask the notification center to observe the notification for UITextFieldTextDidChangeNotification and you should be good,

Given below is the implementation for the same

@IBAction func showAlert(sender: AnyObject) {

    var alert = UIAlertController(title: "New user",
        message: "Add a new user",
        preferredStyle: .Alert)

    let saveAction = UIAlertAction(title: "Save",
        style: .Default) { (action: UIAlertAction!) -> Void in

            println("do your stuff here")
    }

    saveAction.enabled = false

    let cancelAction = UIAlertAction(title: "Cancel",
        style: .Default) { (action: UIAlertAction!) -> Void in
    }


    alert.addTextFieldWithConfigurationHandler {
        (textFieldName: UITextField!) in
        textFieldName.placeholder = "Enter full name"
    }

    alert.addTextFieldWithConfigurationHandler {
        (textFieldEmail: UITextField!) in
        textFieldEmail.placeholder = "Enter valid email adress"
        textFieldEmail.keyboardType = .EmailAddress

    }
// adding the notification observer here
 NSNotificationCenter.defaultCenter().addObserverForName(UITextFieldTextDidChangeNotification, object:alert.textFields?[0],
        queue: NSOperationQueue.mainQueue()) { (notification) -> Void in

            let textFieldName = alert.textFields?[0] as! UITextField
            let textFieldEmail = alert.textFields![1] as! UITextField
            saveAction.enabled = self.isValidEmail(textFieldEmail.text) &&  !textFieldName.text.isEmpty
    }


    NSNotificationCenter.defaultCenter().addObserverForName(UITextFieldTextDidChangeNotification, object:alert.textFields?[1],
        queue: NSOperationQueue.mainQueue()) { (notification) -> Void in

            let textFieldEmail = alert.textFields?[1] as! UITextField
            let textFieldName = alert.textFields?[0] as! UITextField
            saveAction.enabled = self.isValidEmail(textFieldEmail.text) &&  !textFieldName.text.isEmpty
    }


    alert.addAction(saveAction)
    alert.addAction(cancelAction)

    presentViewController(alert,
        animated: true,
        completion: nil)

}

 //  email validation code method
func isValidEmail(testStr:String) -> Bool {
    let emailRegEx = "[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}"
    if let emailTest = NSPredicate(format:"SELF MATCHES %@", emailRegEx) as NSPredicate? {
        return emailTest.evaluateWithObject(testStr)
    }
    return false
}
Bug Hunter Zoro
  • 1,743
  • 18
  • 21
  • Thank you very much, this helped but is acting a little strange. It's ok if I first enter the name and then the email but if I enter the email first and then the name, nothing happens. Also when the email is entered and i delete the text from the name field it still allows me to send the info :S – MarkoDeveloper Jun 02 '15 at 13:45
  • @MarkoMiletić: Hmm I just gave you the idea on how you can do your save button thing give me a moment while i update my answer – Bug Hunter Zoro Jun 02 '15 at 13:49
  • @MarkoMiletić done editing will work as per your need, please review and let me know – Bug Hunter Zoro Jun 02 '15 at 13:54
  • @MarkoMiletić: there was a flaw so re-edited the code please confirm – Bug Hunter Zoro Jun 02 '15 at 13:58
  • Yup, this was the solution, but both text fields needed to be validated in both Notifications as @keithbhunter wrote bellow. Thank you very much :) – MarkoDeveloper Jun 02 '15 at 14:04
  • Yes but other alternative is using the delegate method but as of now NSNotificationCenter would also do – Bug Hunter Zoro Jun 02 '15 at 14:05
3

You can use below code to validate TextFields in an UIAlertController :-

Step 1:

Declare "email_TF" to your viewcontroller.h 

for example: 
    @property(strong,nonatomic)UITextField *email_TF;

Step 2:

UIAlertController *alert= [UIAlertController alertControllerWithTitle:@"Forgot Password?" message:nil preferredStyle:UIAlertControllerStyleAlert];
[alert addTextFieldWithConfigurationHandler: ^(UITextField *textField){
    textField.placeholder= @"Enter Your Valid Email";
    textField.autocorrectionType= UITextAutocorrectionTypeYes;
    textField.keyboardType= UIKeyboardTypeEmailAddress;

    email_TF= textField;
}];

Step 3:

UIAlertAction *noButton= [UIAlertAction actionWithTitle:@"No, thanks" style:UIAlertActionStyleDestructive handler:^(UIAlertAction *action){
    //Handel no, thanks button
}];
[alert addAction:noButton];

UIAlertAction *yesButton= [UIAlertAction actionWithTitle:@"Yes, please" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action){
    //Handel your yes please button action here
    NSLog(@"%@", email_TF.text);

    if(email_TF.text.length>0){//

        NSString *emailString= email_TF.text;
        NSString *emailReg= @"[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}";
        NSPredicate *emailTest= [NSPredicate predicateWithFormat:@"SELF MATCHES %@",emailReg];

        if(([emailTest evaluateWithObject:emailString]!=YES) || [emailString isEqualToString:@""]){

            UIAlertView *loginalert= [[UIAlertView alloc] initWithTitle:@"Forgot Password !" message:@"\nPlease enter valid Email (example@example.com format) ." delegate:self cancelButtonTitle:@"Ok" otherButtonTitles: nil];
            [loginalert show];

        }else{

            NSLog(@"your TextField successfully validated");

        }
    }else{

        UIAlertView *alert= [[UIAlertView alloc] initWithTitle:@"Forgot Password !" message:@"\nPlease Enter Your Email..." delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
        [alert show];

    }

}];

[alert addAction:yesButton];

Step 4:

[self presentViewController:alert animated:YES completion:nil];
Kupendiran iOS
  • 217
  • 2
  • 5
1

Register for the text field change notifications and validate the text fields there:

//...
alert.addTextFieldWithConfigurationHandler {
    (textFieldEmail: UITextField!) in
    textFieldEmail.placeholder = "Enter valid email adress"
    textFieldEmail.keyboardType = .EmailAddress
}   

let textFieldValidationObserver: (NSNotification!) -> Void = { _ in
    let textFieldName = alert.textFields![0] as! UITextField
    let textFieldEmail = alert.textFields![1] as! UITextField
    saveAction.enabled = self.isValidEmail(textFieldEmail.text) && textFieldName.text.length > 0
}

// Notifications for textFieldName changes
NSNotificationCenter.defaultCenter().addObserverForName(UITextFieldTextDidChangeNotification,
    object: alert.textFields![0],  // textFieldName
    queue: NSOperationQueue.mainQueue(), usingBlock: textFieldValidationObserver)

// Notifications for textFieldEmail changes
NSNotificationCenter.defaultCenter().addObserverForName(UITextFieldTextDidChangeNotification,
    object: alert.textFields![1],  // textFieldEmail
    queue: NSOperationQueue.mainQueue(), usingBlock: textFieldValidationObserver)

alert.addAction(saveAction)
//...
Florian Pfisterer
  • 1,205
  • 12
  • 21
keithbhunter
  • 12,258
  • 4
  • 33
  • 58
  • I'm really new to this so i don't understand much of it, could you please implement it in my code? I'm getting an error on the "object: alert.textFields[1]" line, which says "'[AnyObject]' does not have a member named 'subscript'" – MarkoDeveloper Jun 02 '15 at 13:24
  • Updated my answer. Pay attention to the implicitly unwrapped optionals. That is what I missed earlier and what was causing your error. – keithbhunter Jun 02 '15 at 13:35
  • Ohh, thanks dude!!! This did the trick :) I usually work in Frontend development so this sort of things are a little like Chinese to me :D – MarkoDeveloper Jun 02 '15 at 14:02
0

I implemented a UIAlertController subclass which allows you to add a handler on text field changes when you add it to the alert:

public class TextEnabledAlertController: UIAlertController {
  private var textFieldActions = [UITextField: ((UITextField)->Void)]()

  func addTextField(configurationHandler: ((UITextField) -> Void)? = nil, textChangeAction:((UITextField)->Void)?) {
    super.addTextField(configurationHandler: { (textField) in
        configurationHandler?(textField)

        if let textChangeAction = textChangeAction {
            self.textFieldActions[textField] = textChangeAction
            textField.addTarget(self, action: #selector(self.textFieldChanged), for: .editingChanged)

        }
    })
  }

  @objc private func textFieldChanged(sender: UITextField) {
    if let textChangeAction = textFieldActions[sender] {
        textChangeAction(sender)
    }
  }
}

So for your case the only extra thing that need to be added is to call the isValidEmail function in the textChangeAction handler:

    alert.addTextField(configurationHandler: { (textField) in
        // things you want to configure on the textfield
    }) { (textField) in
        saveAction.isEnabled = isValidEmail(textField.text ?? "")
    }
CodeBrew
  • 6,457
  • 2
  • 43
  • 48
0

Following what @Kupendiran presented for email input validation with UIAlertController. Here is a version thats working with Objective-C and the newer UIAlertController format as UIAlertView is now depreciated.

Step 1. add the following to .h and .m files with other properties and variables

.h

@property(strong,nonatomic)UITextField *emailAddressField;

.m

UITextField *emailAddressField;

Step 2. Create the alert message, buttons and validation process.

UIAlertController * alertView =   [UIAlertController
                                           alertControllerWithTitle:@"E-Mail Address"
                                           message:@"Enter your email address:"
                                           preferredStyle:UIAlertControllerStyleAlert];

        [alertView addTextFieldWithConfigurationHandler:^(UITextField *emailTextField) {
            emailTextField.placeholder = @"E-Mail Address";
            emailTextField.autocorrectionType= UITextAutocorrectionTypeYes;
            emailTextField.keyboardType= UIKeyboardTypeEmailAddress;

            emailAddressField = emailTextField;
        }];

Step 3. Create the alert actions

        UIAlertAction * ok= [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action){
            //Handel your OK button action here
            NSLog(@"Email Address Entered is: %@", emailAddressField.text);

            //Validate email address is correct format
            if(emailAddressField.text.length>0){//

                NSString *emailString= emailAddressField.text;
                NSString *emailReg= @"[A-Z0-9a-z._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}";
                NSPredicate *emailTest= [NSPredicate predicateWithFormat:@"SELF MATCHES %@",emailReg];

                if(([emailTest evaluateWithObject:emailString]!=YES) || [emailString isEqualToString:@""]){

                    NSLog(@"Email Address Entered is not valid: %@", emailAddressField.text);

                    UIAlertController *badEmailAlert = [UIAlertController
                                                     alertControllerWithTitle:@"Email Address"
                                                                      message:@"\nPlease enter valid Email (example@example.com format) ."
                                                               preferredStyle:UIAlertControllerStyleAlert];
                    [self presentViewController:badEmailAlert animated:YES completion:nil];

                    UIAlertAction* cancel = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleDefault
                                                                   handler:^(UIAlertAction * action) {
                                                                       [badEmailAlert dismissViewControllerAnimated:YES completion:nil];
                                                                       [self presentViewController:alertView animated:YES completion:nil];
                                                                   }];
                    [badEmailAlert addAction:cancel];


                }else{

                    NSLog(@"your TextField successfully validated");

                }
            }else{

                [self presentViewController:alertView animated:YES completion:nil];

            }

        }];
        [alertView addAction:ok];


        //Handel your Cancel button action here
        UIAlertAction* cancel = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleDefault
                                                       handler:^(UIAlertAction * action) {
                                                           [alertView dismissViewControllerAnimated:YES completion:nil];
                                                       }];
        [alertView addAction:cancel];

Step 4. Present the alert message on the screen

[self presentViewController:alertView animated:YES completion:nil];
Cosworth66
  • 597
  • 5
  • 14
-1

First, you need to add some variables to your class:

private weak var saveAction : UIAlertAction?
private weak var textFieldName : UITextField?
private weak var textFieldEmail : UITextField?
private var validName = false
private var validEmail = false

Then, when you want to configure the alert controller (I only pasted the things that need to be changed):

    alert.addTextFieldWithConfigurationHandler {
        (textFieldName: UITextField!) in
        textFieldName.placeholder = "Enter full name"
        textFieldName.delegate = self
        self.textFieldName = textFieldName
    }
    alert.addTextFieldWithConfigurationHandler {
        (textFieldEmail: UITextField!) in
        textFieldEmail.placeholder = "Enter valid email adress"
        textFieldEmail.keyboardType = .EmailAddress
        textFieldEmail.delegate = self
        self.textFieldEmail = textFieldEmail
    }

    let saveAction = UIAlertAction(title: "Save",
        style: .Default) { (action: UIAlertAction!) -> Void in
        // here you are sure the name and email are correct
        let name = (alert.textFields[0] as! UITextField).text
        let email = (alert.textFields[1] as! UITextField).text
    }

    saveAction.enabled = false
    self.saveAction = saveAction

Finally, you should implement this delegate method:

func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {

    let newText = NSString(string: textField.text).stringByReplacingCharactersInRange(range, withString: string)

    if textField == self.textFieldName {
        // validate newText for the name requirements
        validName = self.validateName(newText)
    } else if textField == self.textFieldEmail {
        // validate newText for the email requirements
        validEmail = self.validateEmail(newText)
    }

    self.saveAction?.enabled = validEmail && validName

    return true
}
Teo
  • 3,394
  • 11
  • 43
  • 73