Предотвратить увольнение UIAlertController

Я добавляю UITextField к UIAlertController, который отображается как AlertView. Прежде чем отклонить UIAlertController, я хочу проверить ввод UITextField. На основании проверки я хочу отклонить UIAlertController или нет. Но я понятия не имею, как предотвратить удаление UIAlertController при нажатии кнопки. Кто-нибудь решил эту проблему или есть идеи, с чего начать? Я пошел в Google, но не повезло: / Спасибо!


person jona jürgen    schedule 02.09.2014    source источник


Ответы (5)


Вы правы: если пользователь может нажать кнопку в вашем оповещении, оповещение будет отклонено. Итак, вы хотите, чтобы пользователь не нажимал кнопку! Это всего лишь вопрос отключения ваших кнопок UIAlertAction. Если действие оповещения отключено, пользователь не может коснуться его, чтобы закрыть.

Чтобы объединить это с проверкой текстового поля, используйте метод делегата текстового поля или метод действия (настроенный в обработчике конфигурации текстового поля при его создании), чтобы соответствующим образом включить/отключить UIAlertActions в зависимости от того, какой текст был (или не был) введен. .

Вот пример. Мы создали текстовое поле следующим образом:

alert.addTextFieldWithConfigurationHandler {
    (tf:UITextField!) in
    tf.addTarget(self, action: "textChanged:", forControlEvents: .EditingChanged)
}

У нас есть действие «Отмена» и действие «ОК», и мы отключили действие «ОК»:

(alert.actions[1] as UIAlertAction).enabled = false

Впоследствии пользователь не может нажать OK, если в текстовом поле нет фактического текста:

func textChanged(sender:AnyObject) {
    let tf = sender as UITextField
    var resp : UIResponder = tf
    while !(resp is UIAlertController) { resp = resp.nextResponder() }
    let alert = resp as UIAlertController
    (alert.actions[1] as UIAlertAction).enabled = (tf.text != "")
}

EDIT Вот текущая (Swift 3.0.1 и более поздние версии) версия приведенного выше кода:

alert.addTextField { tf in
    tf.addTarget(self, action: #selector(self.textChanged), for: .editingChanged)
}

и

alert.actions[1].isEnabled = false

и

@objc func textChanged(_ sender: Any) {
    let tf = sender as! UITextField
    var resp : UIResponder! = tf
    while !(resp is UIAlertController) { resp = resp.next }
    let alert = resp as! UIAlertController
    alert.actions[1].isEnabled = (tf.text != "")
}
person matt    schedule 02.09.2014
comment
Полный пример здесь: github.com/ mattneub/Programming-iOS-Book-Examples/blob/master/ - person matt; 02.09.2014
comment
Есть ли пример Objective-C? - person Adrian; 24.09.2015
comment
Такой великолепный, элегантный ответ. Благодарю вас! Я только что использовал это в проекте Swift. - person Adrian; 15.10.2015
comment
Спасибо @AdrianB, вы сделали мой день. - person matt; 15.10.2015
comment
Кажется, что держать контроллер предупреждений в качестве слабой переменной более надежно, чем танцевать nextResponder. В целом отличное решение!! - person Kaan Dedeoglu; 15.04.2016
comment
Чтобы добавить к этому, вы можете создать замыкание и установить его в качестве цели, а также настроить селектор для вызова. Таким образом, вы можете сохранить все это в одной и той же функции - person Swinny89; 22.07.2016
comment
@Swinny89 Swinny89 Я не представляю, что ты имеешь в виду. Не могли бы вы предоставить это как отдельный ответ? - person matt; 22.07.2016
comment
У меня есть пример на Objective-C, но не на Swift на данный момент - person Swinny89; 22.07.2016
comment
Готово, сегодня вечером я обновлю свой ответ быстрым примером. - person Swinny89; 22.07.2016
comment
@Swinny89 Круто, спасибо! Я с нетерпением жду этого. - person matt; 22.07.2016
comment
Текущая (декабрь 2016 г.) версия кода находится здесь: github.com/mattneub/Programming-iOS-Book-Examples/blob/master/ - person matt; 08.12.2016
comment
Текущая (декабрь 2016 г.) версия кода ... @matt было бы неплохо для такого хорошего ответа увидеть прямо здесь, в StackOverflow, обновленную версию кода. - person Fmessina; 23.04.2018
comment
@Fmessina Текущая версия кода показана прямо здесь, в моем ответе. - person matt; 23.04.2018
comment
Он выдает завершающее приложение из-за необработанного исключения «NSRangeException», причина: «*** -[__NSSingleObjectArrayI objectAtIndex:]: индекс 1 за пределами [0 .. 0]» - person Pawan; 11.09.2018

Я упростил ответ Мэтта без обхода иерархии представлений. Вместо этого это удерживает само действие как слабую переменную. Это полностью рабочий пример:

weak var actionToEnable : UIAlertAction?

func showAlert()
{
    let titleStr = "title"
    let messageStr = "message"

    let alert = UIAlertController(title: titleStr, message: messageStr, preferredStyle: UIAlertControllerStyle.Alert)

    let placeholderStr =  "placeholder"

    alert.addTextFieldWithConfigurationHandler({(textField: UITextField) in
        textField.placeholder = placeholderStr
        textField.addTarget(self, action: "textChanged:", forControlEvents: .EditingChanged)
    })

    let cancel = UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel, handler: { (_) -> Void in

    })

    let action = UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default, handler: { (_) -> Void in
        let textfield = alert.textFields!.first!

        //Do what you want with the textfield!
    })

    alert.addAction(cancel)
    alert.addAction(action)

    self.actionToEnable = action
    action.enabled = false
    self.presentViewController(alert, animated: true, completion: nil)
}

func textChanged(sender:UITextField) {
    self.actionToEnable?.enabled = (sender.text! == "Validation")
}
person ullstrm    schedule 19.03.2016
comment
@ullstrm: Спасибо, это очень помогло. - person Pawan; 11.09.2018

Отрываясь от ответа @Matt, вот как я сделал то же самое в Obj-C.

- (BOOL)textField: (UITextField*) textField shouldChangeCharactersInRange: (NSRange) range replacementString: (NSString*)string
{
    NSString *newString = [textField.text stringByReplacingCharactersInRange: range withString: string];

    // check string length
    NSInteger newLength = [newString length];
    BOOL okToChange = (newLength <= 16);    // don't allow names longer than this

    if (okToChange)
    {
        // Find our Ok button
        UIResponder *responder = textField;
        Class uiacClass = [UIAlertController class];
        while (![responder isKindOfClass: uiacClass])
        {
            responder = [responder nextResponder];
        }
        UIAlertController *alert = (UIAlertController*) responder;
        UIAlertAction *okAction  = [alert.actions objectAtIndex: 0];

        // Dis/enable Ok button based on same-name
        BOOL duplicateName = NO;
        // <check for duplicates, here>

        okAction.enabled = !duplicateName;
    }


    return (okToChange);
}
person Olie    schedule 03.11.2015

Я понимаю, что это в Objectiv-C, но он показывает принцип. Я обновлю это быстрой версией позже.

Вы также можете сделать то же самое, используя блок в качестве цели.

Добавьте свойство в свой ViewController, чтобы блок (замыкание для swift) имел сильную ссылку

@property (strong, nonatomic) id textValidationBlock;

Затем создайте AlertViewController вот так:

UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Title" message:@"Message" preferredStyle:UIAlertControllerStyleAlert];
UIAlertAction *cancelAction = [UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction * _Nonnull action) {

}];

   __weak typeof(self) weakSelf = self;
UIAlertAction *okAction = [UIAlertAction actionWithTitle:@"Ok" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
        [weakSelf doSomething];

}];
[alertController addAction:cancelAction];
[alertController addAction:okAction];
[alertController.actions lastObject].enabled = NO;
self.textValidationBlock = [^{
    UITextField *textField = [alertController.textFields firstObject];
    if (something) {
        alertController.message = @"Warning message";
        [alertController.actions lastObject].enabled = NO;
    } else if (somethingElse) {
        alertController.message = @"Another warning message";
        [alertController.actions lastObject].enabled = NO;
    } else {
        //Validation passed
        alertController.message = @"";
        [alertController.actions lastObject].enabled = YES;
    }

} copy];
[alertController addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
    textField.placeholder = @"placeholder here";
    [textField addTarget:weakSelf.textValidationBlock action:@selector(invoke) forControlEvents:UIControlEventEditingChanged];
}];
[self presentViewController:alertController animated:YES completion:nil];
person Swinny89    schedule 22.07.2016

Здесь та же идея, что и в других ответах, но мне нужен простой метод, изолированный в расширении и доступный для использования в любом подклассе UIViewController. Он показывает оповещение с одним полем ввода текста и двумя кнопками: ок и отмена.

extension UIViewController {

    func askForTextAndConfirmWithAlert(title: String, placeholder: String, okHandler: @escaping (String?)->Void) {
        
        let alertController = UIAlertController(title: title, message: nil, preferredStyle: .alert)
        
        let textChangeHandler = TextFieldTextChangeHandler { text in
            alertController.actions.first?.isEnabled = !(text ?? "").isEmpty
        }
        
        var textHandlerKey = 0
        objc_setAssociatedObject(self, &textHandlerKey, textChangeHandler, .OBJC_ASSOCIATION_RETAIN)

        alertController.addTextField { textField in
            textField.placeholder = placeholder
            textField.clearButtonMode = .whileEditing
            textField.borderStyle = .none
            textField.addTarget(textChangeHandler, action: #selector(TextFieldTextChangeHandler.onTextChanged(sender:)), for: .editingChanged)
        }

        let okAction = UIAlertAction(title: CommonLocStr.ok, style: .default, handler: { _ in
            guard let text = alertController.textFields?.first?.text else {
                return
            }
            okHandler(text)
            objc_setAssociatedObject(self, &textHandlerKey, nil, .OBJC_ASSOCIATION_RETAIN)
        })
        okAction.isEnabled = false
        alertController.addAction(okAction)

        alertController.addAction(UIAlertAction(title: CommonLocStr.cancel, style: .cancel, handler: { _ in
            objc_setAssociatedObject(self, &textHandlerKey, nil, .OBJC_ASSOCIATION_RETAIN)
        }))

        present(alertController, animated: true, completion: nil)
    }

}

class TextFieldTextChangeHandler {
    
    let handler: (String?)->Void
    
    init(handler: @escaping (String?)->Void) {
        self.handler = handler
    }

    @objc func onTextChanged(sender: AnyObject) {
        handler((sender as? UITextField)?.text)
    }
}
person algrid    schedule 23.11.2020