I am using a UIAlertController
to present a dialog with a UITextField
and one UIAlertAction
button labeled \"Ok\". How do I disable th
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)
}
}
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;
}
}
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
}
}
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.
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;
}
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