UIAlertController中的文本字段不应该是第一个响应者

时间:2015-06-10 23:16:06

标签: ios cocoa-touch

我有一个带有文本字段的UIAlertController,如下所示:

let alertController = UIAlertController(title: "Title", message: "Hello, World!", preferredStyle: .Alert)

    let someAction = UIAlertAction(title: "Action", style: .Default) { (_) in }
    let cancelAction = UIAlertAction(title: "Cancel", style: .Cancel) {(_) in }

    alertController.addAction(someAction)
    alertController.addAction(cancelAction)
    alertController.addTextFieldWithConfigurationHandler { textfield in
        textfield.text = "Text"
    }

    self.presentViewController(alertController, animated: true, completion: nil)

当显示控制器时,文本字段具有焦点并且键盘出现。是否可以更改该行为,以便文本字段仅在用户点击它时成为第一响应者?我不希望在出现警报控制器的同时显示键盘。

2 个答案:

答案 0 :(得分:1)

这是一个涉及相关对象和方法混合的轻微hacky解决方案。

这个想法是给UITextField一个闭包,它给外部类一个文本字段是否可以成为第一个响应者的说法。此闭包传回布尔值,指示文本字段是否由于用户交互或由于在文本字段上以编程方式调用canBecomeFirstResponder而成为第一响应者。它还会传递canBecomeFirstResponderdefaultValue参数中正常返回的值 - 在这种情况下不需要这样做,但作为一般解决方案,它可能很有用。

首先,我们添加UITextField扩展程序来处理调配和关联的对象:

public extension UITextField {

    // Private struct to hold our associated object key
    private struct AssociatedKeys {
        static var HandlerKey = "xxx_canBecomeFirstResponder"
    }

    // Typealias for the type of our associated object closure
    public typealias CanBecomeFirstResponderHandler = (fromUserInteraction: Bool,
        defaultValue: Bool) -> Bool

    // We need this private class to wrap the closure in an object
    // because objc_setAssociatedObject takes an 'AnyObject', but
    // closures are not 'AnyObject's -- they are instead 'Any's
    private class AnyValueWrapper {
        var value: Any?
    }

    // Define the closure as a computed property and use associated objects to
    // store/retrieve it.
    public var canBecomeFirstResponderHandler: CanBecomeFirstResponderHandler? {
        get {
            // Get the AnyValueWrapper object
            let wrapper = objc_getAssociatedObject(self,
                                                   &AssociatedKeys.HandlerKey)

            // ...then get the closure from its `value` property
            return (wrapper as? AnyValueWrapper)?.value
                as? CanBecomeFirstResponderHandler
        }

        set {
            // If the new value is not nil:
            if let newValue = newValue {

                // Create a new AnyValueWrapper and set its `value` property to 
                // the new closure
                let wrapper = AnyValueWrapper()
                wrapper.value = newValue

                // Set this wrapper object as an associated object
                objc_setAssociatedObject(
                    self,
                    &AssociatedKeys.HandlerKey,
                    wrapper,
                    .OBJC_ASSOCIATION_RETAIN_NONATOMIC
                )

                return
            }

            // If the new value is nil, remove any existing associated object for
            // the closure
            objc_setAssociatedObject(
                self,
                &AssociatedKeys.HandlerKey,
                nil,
                .OBJC_ASSOCIATION_RETAIN_NONATOMIC
            )
        }
    }

    // Set up the method swizzling when the `UITextField` class is initialized
    public override class func initialize() {
        struct Static {
            static var token: dispatch_once_t = 0
        }

        // Make sure we are not in a subclass when this method is called
        if self !== UITextField.self {
            return
        }

        // Swizzle the canBecomeFirstResponder method.
        dispatch_once(&Static.token) {
            let originalSelector =
                #selector(UITextField.canBecomeFirstResponder)
            let swizzledSelector =
                #selector(UITextField.xxx_canBecomeFirstResponder)

            let originalMethod = class_getInstanceMethod(self, originalSelector)
            let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)

            let didAddMethod =
                class_addMethod(self,
                                originalSelector,
                                method_getImplementation(swizzledMethod),
                                method_getTypeEncoding(swizzledMethod))

            if didAddMethod {
                class_replaceMethod(self, swizzledSelector,
                                    method_getImplementation(originalMethod),
                                    method_getTypeEncoding(originalMethod))
            }
            else {
                method_exchangeImplementations(originalMethod, swizzledMethod)
            }
        }
    }

    // MARK: - Method Swizzling

    // Our swizzled method that replaces the canBecomeFirstResponder 
    // method of `UITextField`
    func xxx_canBecomeFirstResponder() -> Bool {

        // Get the default value of canBecomeFirstResponder
        let defaultValue = xxx_canBecomeFirstResponder()

        // If we have a closure in our associated object:
        if let canBecomeFirstResponder = canBecomeFirstResponderHandler {

            // Determine if the user interacted with the text field and set
            // a flag if so. We do this by checking all gesture recognizers
            // of the text field to see if any of them have begun, changed, or
            // ended at the time of calling `canBecomeFirstResponder`.
            // It's reasonable to assume that if `canBecomeFirstResponder` is
            // called when any of these conditions are true, then the text field
            // must be trying to become the first responder due to a user
            // interaction.
            var isFromUserInteraction = false
            if let gestureRecognizers = gestureRecognizers {
                for gestureRecognizer in gestureRecognizers {
                    if (gestureRecognizer.state == .Began ||
                        gestureRecognizer.state == .Changed ||
                        gestureRecognizer.state == .Ended)
                    {
                        isFromUserInteraction = true
                        break
                    }
                }
            }

            // Call our closure and pass in the two boolean values,
            // then return the result
            return canBecomeFirstResponder(
                fromUserInteraction: isFromUserInteraction,
                defaultValue: defaultValue
            )
        }

        // If we don't have a closure in our associated object,
        // just return the original value
        return defaultValue
    }
}

然后,您可以在警报控制器文本字段配置处理程序中使用此闭包,如下所示:

alertController.addTextFieldWithConfigurationHandler { textfield in
    textfield.text = "Text"

    // Set the closure on the text field. You can use the passed in flags if you
    // want or you can simply return fromUserInteraction to only allow user
    // interaction to let the text field become the first responder, as is done
    // here:
    textfield.canBecomeFirstResponderHandler = {
        fromUserInteraction, defaultValue in
        return fromUserInteraction
    }
}

不再有效的旧解决方案:

添加:

alertController.view.endEditing(true)
在提示警报控制器之前,

将从所有文本字段中退出第一响应者并阻止键盘在呈现时出现。

答案 1 :(得分:1)

我使用了一个更简单的技巧。我创建了一个名为canEdit的布尔值并将其设置为NO,并且只在完成“当前视图控制器”方法时将其设置为YES。在textfield应该开始编辑,我根据canEdit boolean设置一个条件是否返回YES或NO。这是我的代码:

@interface ViewController (){

    BOOL canEdit; 

}

-(void)viewDidLoad{

[super viewDidLoad];

__weak typeof(self) weakSelf = self;
mentorAlert = [UIAlertController alertControllerWithTitle:@"Magen David Students" message:@"Hi! Please select your mentor!" preferredStyle:UIAlertControllerStyleAlert];
            [mentorAlert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {

                textField.placeholder = @"Mentor";
                textField.delegate = weakSelf;
                textField.tag = 200;
                textField.userInteractionEnabled = YES;


            }];

            [self presentViewController:mentorAlert animated:YES completion:^{

                canEdit = YES;


            }];



}

-(BOOL)textFieldShouldBeginEditing:(UITextField *)textField{
if(textField.tag == 200){

    if (!canEdit)
            return NO;
else
return YES;
}
}