避免被UIAlertController解雇

54

我将一个UITextField添加到UIAlertController中,该UIAlertControllerAlertView的形式显示。在解散UIAlertController之前,我想要验证UITextField的输入。根据验证结果,我希望解散UIAlertController或不解散。但是我不知道如何在按下按钮时阻止UIAlertController的解散操作。有人解决了这个问题或者有任何可以开始的想法吗?我去谷歌搜索但没有找到:/谢谢!

5个回答

80

您说得对:如果用户可以点击警报中的按钮,则会关闭警报。因此,您希望防止用户点击按钮!只需要禁用UIAlertAction按钮即可。如果禁用了警报操作,用户就无法点击它来关闭。

要将其与文本字段验证相结合,请使用文本字段委托方法或操作方法(在创建文本字段时配置在文本字段的配置处理程序中),根据输入的文本情况启用/禁用UIAlertActions。

下面是一个示例。我们像这样创建了文本字段:

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

我们有一个取消操作和一个确认操作,我们将确认操作禁用了。
(alert.actions[1] as UIAlertAction).enabled = false

因此,除非文本字段中有某些实际文本,否则用户无法点击“确定”按钮:

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 != "")
}

编辑 这是上述代码的当前版本(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 != "")
}

完整示例在此处:https://github.com/mattneub/Programming-iOS-Book-Examples/blob/master/bk2ch13p620dialogsOniPhone/ch26p888dialogsOniPhone/ViewController.swift - matt
1
这里有没有 Objective-C 的相关示例呢? - Adrian
这是一个非常漂亮、优雅的答案。谢谢!我刚在一个 Swift 项目中使用了它。 - Adrian
谢谢@AdrianB,你让我的一天变得美好。 - matt
1
感觉将警报控制器作为弱变量保留比执行nextResponder舞蹈更可靠。总体来说是一个很好的解决方案!! - Kaan Dedeoglu
显示剩余9条评论

21

我已经简化了马特的答案,而不需要遍历视图结构。这是将操作本身保持为弱变量的方法。以下是完整的可行示例:

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")
}

1
@ullstrm:谢谢,这帮了很多忙。 - Pawan

7
借鉴@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);
}

5

我知道这是用Objective-C编写的,但它表明了原则。稍后我会更新Swift版本。

您也可以使用块作为目标来执行相同的操作。

向您的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];

0
这里与其他答案的想法相同,但我希望在扩展中隔离出一个简单的方法,并可用于任何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)
    }
}

谢谢,这是唯一一个对我有效的版本。如果两个UITextField都必须有文本才能启用“确定”按钮,您如何做到这一点? - Neph

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接