Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UITapGestureRecognizer tap on self.view but ignore subviews

I need to implement a feature that will invoke some code when I double tap on the self.view (view of UIViewController). But the problem that I have other UI object on this view and I don't want to attach any recognizer object to all of them. I found this method below how to make gesture on my view and I know how it works. Right now I am in front of handicap which way to choose for create this recognizer ignoring subview. Any ideas? Thanks.

UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleDoubleTap:)];
[doubleTap setNumberOfTapsRequired:2];
[self.view addGestureRecognizer:doubleTap];
like image 225
Matrosov Oleksandr Avatar asked Apr 04 '13 14:04

Matrosov Oleksandr


7 Answers

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
}
like image 70
Ravi Avatar answered Nov 10 '22 14:11

Ravi


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
}
like image 127
Ian Avatar answered Nov 10 '22 12:11

Ian


And for the Swift variant:

func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldReceiveTouch touch: UITouch) -> Bool {
    if touch.view.isDescendantOfView(yourSubView){
        return false
    }
    return true
}

Good to know, isDescendantOfView returns a Boolean value indicating whether the receiver is a subview of a given view or identical to that view.

like image 24
Antoine Avatar answered Nov 10 '22 14:11

Antoine


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.

like image 18
Imanou Petit Avatar answered Nov 10 '22 13:11

Imanou Petit


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
        }
    }
}
like image 10
Arru Avatar answered Nov 10 '22 13:11

Arru


Variant using CGPoint you touch (SWIFT 4.0)

class MyViewController: UIViewController, UIGestureRecognizerDelegate {

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

// Get the location in CGPoint
    let location = touch.location(in: nil)

// Check if location is inside the view to avoid
    if viewToAvoid.frame.contains(location) {
        return false
    }

    return true
  }
}
like image 7
CoachThys Avatar answered Nov 10 '22 12:11

CoachThys


Clear Swift way

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    return touch.view == self.view
}
like image 5
Musa almatri Avatar answered Nov 10 '22 12:11

Musa almatri