UITapGestureRecognizer tap on self.view but ignore subviews

后端 未结 12 786
佛祖请我去吃肉
佛祖请我去吃肉 2020-11-30 21:49

I need to implement a feature that will invoke some code when I double tap on the self.view (view of UIViewCotroller). But the problem that I have other UI obje

相关标签:
12条回答
  • 2020-11-30 22:17

    You should adopt the UIGestureRecognizerDelegate protocol inside the self object and call the below method for checking the view. Inside this method, check your view against touch.view and return the appropriate bool (Yes/No). Something like this:

    - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
    {
        if ([touch.view isDescendantOfView:yourSubView]) {
            return NO;
        }
        return YES;
    }
    

    Edit: Please, also check @Ian's answer!

    Swift 5

    // MARK: UIGestureRecognizerDelegate methods, You need to set the delegate of the recognizer
    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
         if touch.view?.isDescendant(of: tableView) == true {
            return false
         }
         return true
    }
    
    0 讨论(0)
  • 2020-11-30 22:17

    Another approach is to just compare if the view of the touch is the gestures view, because descendants won't pass the condition. A nice, simple one-liner:

    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        return touch.view == gestureRecognizer.view
    }
    
    0 讨论(0)
  • 2020-11-30 22:17

    Complete swift solution (delegate must be implemented AND set for recognizer(s) ):

    class MyViewController: UIViewController UIGestureRecognizerDelegate {
    
        override func viewDidLoad() {
            let doubleTapRecognizer = UITapGestureRecognizer(target: self, action: #selector(onBaseTapOnly))
            doubleTapRecognizer.numberOfTapsRequired = 2
            doubleTapRecognizer.delegate = self
            self.view.addGestureRecognizer(doubleTapRecognizer)
        }
    
        func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
            if touch.view.isDescendantOfView(self.view){
                return false
            }
            return true
        }
    
        func onBaseTapOnly(sender: UITapGestureRecognizer) {
            if sender.state == .Ended {
                //react to tap
            }
        }
    }
    
    0 讨论(0)
  • 2020-11-30 22:20

    Note that the gestureRecognizer API has changed to:

    gestureRecognizer(_:shouldReceive:)

    Take particular note of the underscore (skip) indicator for the first parameter's external label.

    Using many of the examples provided above, I was not receiving the event. Below is a an example that works for current versions of Swift (3+).

    public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        var shouldReceive = false
        if let clickedView = touch.view {
            if clickedView == self.view {
                shouldReceive = true;
            }
        }
        return shouldReceive
    }
    
    0 讨论(0)
  • 2020-11-30 22:21

    With Swift 5 and iOS 12, UIGestureRecognizerDelegate has a method called gestureRecognizer(_:shouldReceive:). gestureRecognizer(_:shouldReceive:) has the following declaration:

    Ask the delegate if a gesture recognizer should receive an object representing a touch.

    optional func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool
    

    The complete code below shows a possible implementation for gestureRecognizer(_:shouldReceive:). With this code, a tap on a subview of the ViewController's view (including imageView) won't trigger the printHello(_:) method.

    import UIKit
    
    class ViewController: UIViewController, UIGestureRecognizerDelegate {
    
        override func viewDidLoad() {
            super.viewDidLoad()
    
            let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(printHello))
            tapGestureRecognizer.delegate = self
            view.addGestureRecognizer(tapGestureRecognizer)
    
            let imageView = UIImageView(image: UIImage(named: "icon")!)
            imageView.frame = CGRect(x: 50, y: 50, width: 100, height: 100)
            view.addSubview(imageView)
    
            // ⚠️ Enable user interaction for imageView so that it can participate to touch events.
            // Otherwise, taps on imageView will be forwarded to its superview and managed by it.
            imageView.isUserInteractionEnabled = true
        }
    
        func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
            // Prevent subviews of a specific view to send touch events to the view's gesture recognizers.
            if let touchedView = touch.view, let gestureView = gestureRecognizer.view, touchedView.isDescendant(of: gestureView), touchedView !== gestureView {
                return false
            }
            return true
        }
    
        @objc func printHello(_ sender: UITapGestureRecognizer) {
            print("Hello")
        }
    
    }
    

    An alternative implementation of gestureRecognizer(_:shouldReceive:) can be:

    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        return gestureRecognizer.view === touch.view
    }
    

    Note however that this alternative code does not check if touch.view is a subview of gestureRecognizer.view.

    0 讨论(0)
  • 2020-11-30 22:23

    Swift 4:

    touch.view is now an optional, so based on @Antoine's answer:

    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        if let touchedView = touch.view, touchedView.isDescendant(of: deductibleBackgroundView) {
            return false
        }
        return true
    }
    
    0 讨论(0)
提交回复
热议问题