Reputation: 27113
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];
Upvotes: 112
Views: 77252
Reputation: 1
Another solution may be found with the hitTest
function, which
Returns the farthest descendant of the receiver in the view hierarchy (including itself) that contains a specified point.
By this way defining the UIGestureRecognizerDelegate
is not necessary:
@objc func tapFunction (_ recognizer : UITapGestureRecognizer) {
if let viewTouched = recognizer.view?.hitTest(recognizer.location(in: recognizer.view), with: nil) {
if viewTouched == self.parentView {
//the parent view is tapped!
}
}
}
Upvotes: 0
Reputation: 1516
If you don't want your 'double-tap recogniser' to conflict with your buttons and/or other controls, you can set self
as UIGestureRecognizerDelegate
and implement:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool
{
return !(touch.view is UIControl)
}
Upvotes: 1
Reputation: 8309
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
}
Upvotes: 183
Reputation: 92409
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
.
Upvotes: 18
Reputation: 5842
Clear Swift way
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
return touch.view == self.view
}
Upvotes: 5
Reputation: 59
I had to prevent the gesture on the child view. The only thing that worked is to allow and keep the first view and prevent gesture in all the next views:
var gestureView: UIView? = nil
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
if (gestureView == nil || gestureView == touch.view){
gestureView = touch.view
return true
}
return false
}
Upvotes: 2
Reputation: 1751
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
}
Upvotes: 1
Reputation: 280
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
}
}
Upvotes: 7
Reputation: 3750
Plus the above solutions, do not forget to check User Interaction Enabled
of your sub-view.
Upvotes: 2
Reputation: 12758
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
}
Upvotes: 131
Reputation: 63
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
}
Upvotes: 3
Reputation: 163
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
}
}
}
Upvotes: 10
Reputation: 23976
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.
Upvotes: 23