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

我有一个带有文本字段的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)

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

最佳答案 这是一个涉及关联对象和方法调配的轻微hacky解决方案.

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

首先,我们添加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)

在呈现警报控制器之前,将从所有文本字段中退出第一响应者并防止键盘在呈现时出现.

点赞