Search code examples
iosswiftuitapgesturerecognizer

UIButton is not affected by UITapGestureRecoginzer.


I have a UIImageView (userInteractionEnabled) UIButton and UITextField both are subviews of superview, which has a UITapGestureRecognizer add to it. So my superview and UIImageView are responds to tap gestures but UIButton and UITextField ignores it. Is it a policy of UITapGestureRecognizer added to superview of this is my fault? Does anyone know about this situation?

This is my code with view subviews and gestureRecognizer

 @objc   func tapBlurButton(_ sender: UITapGestureRecognizer) {
    print("Please Help!")
    endEditing(true)

}


override init(frame: CGRect) {
    super.init(frame: frame)
   //     backgroundColor = UIColor.green

    addSubview(logoImageView)
   // addSubview(skipButton)
   addSubview(emailTextField)
   addSubview(passwordTextField)
    addSubview(loginButton)
     observeKeyboardNotifications()


 //   logoImageView.translatesAutoresizingMaskIntoConstraints = false
    logoImageView.anchorWithConstantsToTop(centerYAnchor, left: nil  , bottom: nil, right: nil, topConstant: -230, leftConstant: 0, bottomConstant: 0, rightConstant: 0)
    logoImageView.widthAnchor.constraint(equalToConstant: 160).isActive = true
    logoImageView.heightAnchor.constraint(equalToConstant: 160).isActive = true
    logoImageView.centerXAnchor.constraint(equalTo: centerXAnchor).isActive = true

    emailTextField.anchorWithConstantsToTop(logoImageView.bottomAnchor, left: leftAnchor, bottom: nil, right: rightAnchor, topConstant: 8, leftConstant: 32, bottomConstant: 0, rightConstant: 32)
    emailTextField.heightAnchor.constraint(equalToConstant: 50).isActive = true
    passwordTextField.anchorWithConstantsToTop(emailTextField.bottomAnchor, left: leftAnchor, bottom: nil, right: rightAnchor, topConstant: 8, leftConstant: 32, bottomConstant: 0, rightConstant: 32)
    passwordTextField.heightAnchor.constraint(equalToConstant: 50).isActive = true
    loginButton.anchorWithConstantsToTop(passwordTextField.bottomAnchor, left: leftAnchor, bottom: nil, right: rightAnchor, topConstant: 16, leftConstant: 32, bottomConstant: 0, rightConstant: 32)
    loginButton.heightAnchor.constraint(equalToConstant: 50).isActive = true
    isUserInteractionEnabled = true
    addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(LoginCell.tapBlurButton(_:))))
    isUserInteractionEnabled = true

 }

I saw other answers they are just tells how to workaround it , but they don't thouch on real reason why it happens, moreover no one refers to docs that tell about this issue exactly. So my question does not asks how to avoid it or how to resolve it, i want to know why it happens, and am i right when i suspect UIkit of doing that so this not my fault or i am wrong a had missed something?


Solution

  • There is a really simple and helpful article from Apple. And to understand what you want you should look at the Responder Chain concept.

    Understanding Event Handling, Responders, and the Responder Chain

    Basically, if you want to understand why your UIImageView responds to taps, but UIButton and UITextField don't do it, there is a simple explanation. UIButton and UITextField are UIControl's subclasses, but UIImageView is not, it's only UIResponder's subclass. So, every UIControl uses The Target-Action Mechanism, but a UIResponder doesn't do it.

    UIControl docs

    enter image description here

    And the documentation says:

    Controls communicate directly with their associated target object using action messages. When the user interacts with a control, the control calls the action method of its target object—in other words, it sends an action message to its target object. Action messages are not events, but they may still take advantage of the responder chain. When the target object of a control is nil, UIKit starts from the target object and walks the responder chain until it finds an object that implements the appropriate action method.

    So, it means that by default when you click on a UIControl object, it doesn't propagate that event to the superview to handle it. Controls override default UIResponder behavior. But when you click on a UIResponder, it sends the event to the superview to handle it, the touch falls through. So, UIKit does that and usual UITapGestureRecognizers don't go through UIControls by default, since controls have their set of preset events and use another mechanism of handling them. But when you disable UIControl's ability to interact with user taps, your superview tap gesture recognizer will respond.

    In your case, if you set that, your func tapBlurButton(_ sender: UITapGestureRecognizer) will be called even if you press the button, because that button won't try to handle the event and the touch will just fall through to the superview.

    loginButton.isUserInteractionEnabled = false