saintjab
saintjab

Reputation: 1642

Enable UIAlertAction of UIAlertController only after input is validated

I am using a UIAlertController to present a dialog with a UITextField and one UIAlertAction button labeled "Ok". How do I disable the button until a number of characters (say 5 characters) are input into the UITextField?

Upvotes: 13

Views: 9185

Answers (6)

Imanou Petit
Imanou Petit

Reputation: 92409

With Swift 5.3 and iOS 14, you can use Combine framework and NotificationCenter to track UITextField.textDidChangeNotification notifications for a given UITextField.


The following code shows a possible implementation in order to enable the button of a UIAlertController according to the character count of its textField:

import UIKit
import Combine

class ViewController: UIViewController {

    var cancellable: AnyCancellable?

    override func viewDidLoad() {
        super.viewDidLoad()

        view.backgroundColor = .systemBackground

        let action = UIAction(
            title: "Change title",
            handler: { [unowned self] _ in
                self.presentAlert()
            }
        )
        let barButtonItem = UIBarButtonItem(primaryAction: action)
        navigationItem.rightBarButtonItem = barButtonItem
    }

}
extension ViewController {

    func presentAlert() {
        let alertController = UIAlertController(
            title: "Change title",
            message: nil,
            preferredStyle: .alert
        )
        let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { _ in
            print("Cancelled")
        }
        let renameAction = UIAlertAction(
            title: "Rename",
            style: .default
        ) { [unowned alertController] action in
            print("Renamed: \(alertController.textFields!.first!.text!)")
        }
        renameAction.isEnabled = false

        alertController.addAction(cancelAction)
        alertController.addAction(renameAction)
        alertController.addTextField(configurationHandler: { textField in
            self.cancellable = NotificationCenter.default
                .publisher(for: UITextField.textDidChangeNotification, object: textField)
                .sink(receiveValue: { _ in
                    let textCount = textField.text?.trimmingCharacters(in: .whitespacesAndNewlines).count ?? 0
                    renameAction.isEnabled = textCount >= 5 // min 5 characters
                })
        })
        present(alertController, animated: true)
    }

}

Upvotes: 1

CodeBrew
CodeBrew

Reputation: 7187

I had an answer for another post asking basically the same question on stackoverflow. To summarize, there are several ways to do this: use UITextFieldDelegate, Notification, KVO, or plainly add event handling target on the control. My solution is a simple UIAlertController subclass wrapped around the event handling target that you can configure simply by calling

    alert.addTextField(configurationHandler: { (textField) in
        textField.placeholder = "Your name"
        textField.autocapitalizationType = .words
    }) { (textField) in
        saveAction.isEnabled = (textField.text?.characters.count ?? 0) > 0
    }

This should be convenient if you have to deal with such alerts more than a few times in the project.

Upvotes: 0

Scott Gardner
Scott Gardner

Reputation: 8739

Swift 3 implementation based on soulshined's answer:

var someAlert: UIAlertController {
    let alert = UIAlertController(title: "Some Alert", message: nil, preferredStyle: .alert)

    alert.addTextField {
        $0.placeholder = "Write something"
        $0.addTarget(self, action: #selector(self.textFieldTextDidChange(_:)), for: .editingChanged)
    }

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

    let submitAction = UIAlertAction(title: "Submit", style: .default) { _ in
        // Do something...
    }

    submitAction.isEnabled = false
    alert.addAction(submitAction)
    return alert
}

func textFieldTextDidChange(_ textField: UITextField) {
    if let alert = presentedViewController as? UIAlertController,
        let action = alert.actions.last,
        let text = textField.text {
        action.isEnabled = text.characters.count > 0
    }
}

Upvotes: 8

Abdurrahman Mubeen Ali
Abdurrahman Mubeen Ali

Reputation: 1341

A better approach would be to alert the user about what is wrong with his input after validating his input, so that the user knows what the app is expecting from him.

- (void)askReasonWithPreviousReason:(NSString *)text
{
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Alert" message:@"Enter reason" preferredStyle:UIAlertControllerStyleAlert];

    [alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField)
     {
         textField.text = text;
     }];

    [alertController addAction:[UIAlertAction actionWithTitle:@"Save" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action)
                                {
                                    if ([self isReasonValid:alertController.textFields.firstObject.text])
                                    {
                                        UIAlertController *alertController2 = [UIAlertController alertControllerWithTitle:AlertTitle message:@"Are you sure you would like to save?" preferredStyle:UIAlertControllerStyleAlert];

                                        [alertController2 addAction:[UIAlertAction actionWithTitle:@"Yes" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action)
                                                                     {
                                                                         [self saveReason:alertController.textFields.firstObject.text];
                                                                     }]];
                                        [alertController2 addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:Nil]];

                                        [self presentViewController:alertController2 animated:YES completion:nil];
                                    }
                                }]];

    [alertController addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:Nil]];

    [self presentViewController:alertController animated:YES completion:nil];
}

- (BOOL)isReasonValid:(NSString *)reason
{
    NSString *errorMessage = [[NSString alloc] init];

    if (reason.length < 5)
    {
        errorMessage = @"Reason must be more than 5 characters";
    }
    else if (reason.length > 100)
    {
        errorMessage = @"Reason must be less than 100 characters";
    }

    if (errorMessage.length != 0)
    {
        UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Alert" message:errorMessage preferredStyle:UIAlertControllerStyleAlert];

        [alertController addAction:[UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action)
                                    {
                                        [self askReasonWithPreviousReason:reason];
                                    }]];

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

        return NO;
    }

    return YES;
}

Upvotes: 0

Mohammed Shakeer
Mohammed Shakeer

Reputation: 1504

Add following property in your header file

@property(nonatomic, strong)UIAlertAction *okAction;   

then copy the following code in your viewDidLoad method of your ViewController

self.okAction = [UIAlertAction actionWithTitle:@"OK"
                                         style:UIAlertActionStyleDefault
                                       handler:nil];
self.okAction.enabled = NO;

UIAlertController *controller = [UIAlertController alertControllerWithTitle:nil
                                                                    message:@"Enter your text"
                                                             preferredStyle:UIAlertControllerStyleAlert];

[controller addTextFieldWithConfigurationHandler:^(UITextField *textField) {

    textField.delegate = self;
}];

[controller addAction:self.okAction];
[self presentViewController:controller animated:YES completion:nil];

Also implement the following UITextField delegate method in your Class

- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{

    NSString *finalString = [textField.text stringByReplacingCharactersInRange:range withString:string];
   [self.okAction setEnabled:(finalString.length >= 5)];
   return YES;
}

This should work

Upvotes: 17

soulshined
soulshined

Reputation: 10592

You can add an observer to your UITextField:

[alertController addTextFieldWithConfigurationHandler:^(UITextField *textField) {
    [textField addTarget:self action:@selector(alertControllerTextFieldDidChange:) forControlEvents:UIControlEventEditingChanged];
}

but first disable your button:

okAction.enabled = NO;

Then validate it in the method you specified :

- (void)alertTextFieldDidChange:(UITextField *)sender {
  UIAlertController *alertController = (UIAlertController *)self.presentedViewController;
  if (alertController) {
    UITextField *someTextField = alertController.textFields.firstObject;
    UIAlertAction *okAction = alertController.actions.lastObject;
    okAction.enabled = someTextField.text.length > 2;
  }
}

Upvotes: 18

Related Questions