I have a UIViewController object (bottomViewController) in the window, and a UIView object (upperView) on the UIViewController.There are some buttons (buttonOne, buttonTwo, buttonThree) on the UIView. The bottomViewController have a UIGestureRecognizer.
Now, I have a touch event on the upperView; the touch point is outside of the buttons. The upperView will pass the event to its superview (bottomViewController).
How can I prevent upperView from passing the event - to which it can't respond - to its superview?
Make use of:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
int the UIGestureRecognizerDelegate
return YES if you should receive touch NO if not, based on what ever logic you want to apply. You have the UITouch and UIGestureRecognizer which both contain lots of useful information.
Remember to set delegate of UIGestureRecognizer to self.
You could assign tags to views and then check for if condition and put up logic into it.
Related
I have a swipe gesture attached to a UIView that doesn't seem to be registering when the swipe is on top of it's subviews.
One of solution is to check is gesture point inside your subview or not,
there is a useful C function:
/* Return true if `point' is contained in `rect', false otherwise. */
bool CGRectContainsPoint(CGRect rect, CGPoint point)
that you can use like this:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
return CGRectContainsPoint(subview.frame, [touch locationInView:self.view]);;
}
The other solutions should work, but a potentially easier one is to set subview.userInteractionEnabled = false in the subview if it doesn't have its own event handlers.
Let's say A is the root UIView which you want to receive swipes, and B is a subview of A that you don't really want to receive swipes.
if you do not want to receive any gestures on B, you can userInteractionEnabled = false on it
if you still want to receive some gestures on B (but not a swipe)
you must subclass B so that you can implement this method, and implement this method in B
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
if ([gestureRecognizer isKindOfClass:UISwipeGestureRecognizer]) {
return NO;
}
return YES;
}
You can also see how you may get a wide range of functionality from implementing gestureRecognizer:shouldReceiveTouch:
Suppose I have a UIView named Cake. Cake has a gesture recognizer.
Now, suppose I have a UIButton named Bob.
I add Cake as a subview to Bob:
[Bob addSubview: Cake];
Now, Bob, the UIButton, no longer responds the control event touch up inside.
I want Cake to be able to handle the touch while Bob simultaneously handles the touch as well. Currently, Cake can handle the touch, but Bob lazily does nothing.
Things I have tried:
Setting cancelsTouchesInView of Cake's gesture recognizer to NO
Implementing the UIGestureRecognizerdelegate for Cake's gesture recognizer and always returning YES for the shouldRecognizeSimultaneouslyWithGestureRecognizer method
Subclassing UIGestureRecognizer and calling [self.view.nextResponder touchesSomething:touches withEvent:event]; in each of the touchesSomething (touchesBegan, touchesEnded, etc.) methods (I've also confirmed that the next responder IS IN FACT the UIButton that is supposed to handle the control events)
Not using a gesture recognizer and instead just using the touchesSomething methods in the UIView (Cake) + passing through the touchesSomething calls to all of super, self.superview, self.nextResponder and more.
Does anyone know a good way to make this work?
My suggestion is the following:
Make sure, that you set the UserInteraction of the subview to false! Now the action of the button should be called correctly.
Now add the event parameter to the action:
- (IBAction)pressButton:(UIButton *)sender forEvent:(UIEvent *)event
Inside the action check if the press was inside the subview
- (IBAction)pressButton:(UIButton *)sender forEvent:(UIEvent *)event
// get location
UITouch *touch = [[event allTouches] anyObject];
CGPoint location = [touch locationInView:touch.view];
// check position
if (CGRectContainsPoint(self.subview.frame, location) {
// call selector like the gesture recognizer here
}
}
What I'm trying to make, Cake, is a view that can be placed as a subview into any button without additional setup - its a decorative view of sorts. The gesture recognizer of Cake is there to make a small animation
Then you're going about this all wrong. Take away the gesture recognizer of Cake; you don't need it. You're trying to get Cake to respond to Bob being pressed. But that's easy; Bob's a button! The button already tells you everything that's happening — it's being highlighted etc. So all you need is a UIButton subclass that tells Cake when to do its animation.
UITapGestureRecognizer is applied to both UIImageView and its subview (UITextView). However, when I tap on subview, the receiver becomes subview and its parent view (i.e. UIImageView + UITextView). It should however be only subview because that was the one I tapped. I was assuming nested gestures would react first but apparently parent receives the fist tap and then it goes to child.
So, there are different solutions out there for various scenarios (not similar to mine but rather buttons inside scroll view conflict). How can I easily fix my issue without possible subclassing and for iOS 6+ support? I tried delaying touch on start for UIGestureRecognizer on UIImageView and I tried setting cancelsTouchesInView to NO - all with no luck.
Try the following code:
conform the <UIGestureRecognizerDelegate> to your class.
set yourGesture.delegate = self;
then add this delegate Method:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
// return YES (the default) to allow the gesture recognizer to examine the touch object, NO to prevent the gesture recognizer from seeing this touch object.
if([touch.view isKindOfClass: [UITextView class]] == YES)] {
return YES;
}
else {
return NO;
}
}
Hope it will solve your issue. Enjoy Coding..!!!!
That's exactly what is it supposed to do.
View hierarchy is like a tree structure and its traversal during a touch gesture starts from the root node. It is very likely for your parent view to receive gesture first and then its subviews. The traversal skips the nodes for which
userInteractionEnabled = NO.
since, you don't have any code I can't help you to play with this flag. A more general solution is to always set gesture only for your parentView and in the gesture delegates check the coordinates if it belongs to any one of the subview and if yes then call your gesture method for your subview.
Not a clean approach but works. !!
you should implement the UIGestureRecognizer delegate methods and apply the correct policy to the gesture, when multiple gesture are recognized
In the GIKAnimatedCallout sample code, double-tap gestures, two-finger tap gestures, zoom gestures, and pinch gestures are all being passed through from the UITableView to the MKMapView underneath it. I want to stop this from happening. Touch events inside the UITableView should not be passed onto the MKMapView.
I've tried adding a UIGestureRecognizer for taps, and made it an empty method, but these touch events are still sent to the MKMapView as well as the UITableView.
Looking at the UITableView's superview hierarchy through the debugger, I see that the UITableView is a descendant of the MKMapView.
I don't really know how else to approach this problem. Any pointers are appreciated.
if i understand correctly, you want the MKMapView not to react at all if a gesture is made on the UITableView (in the example, the myTableView property of myMKMapView is the UITableView in question). if so, you should implement
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch;
in your subclass of MKMapView (make a subclass if you dont have one. i've called the subclass myMKMapView in this example). Start off by making your subclass of MKMapView, myMKMapView, conform to the UIGestureRecognizerDelegate protocol.
in the MKMapView's .h file:
#interface myMKMapView : MKMapView <UIGestureRecognizerDelegate>
After this is done, go into myMKMapView's .m file and implement the following as such:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
UIView *view = [self.view hitTest:[touch locationInView:self.view] withEvent:nil];
if ([view isDescendantOfView:(self.myTableView)]) {
return NO;
}
return YES;
}
This tells myMKMapView that if a gesture is performed on its myTableView property, myMKMapView should ignore it.
Swift 5 version of Pedro Cattori's answer.
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
!(view.hitTest(touch.location(in: view), with: nil)?.isDescendant(of: tableView) == true)
}
I have a view with a tap gesture and a UIButton on it with its corresponding action.
The problem is that If I press the button its corresponding action is not getting called instead the tap handler is getting called.
I did a hitTest and stopped the gesture-handler from doing anything if the hit was on the button.
But I am unable to let the button's action do something.
You can set your controller class to be the delegate of the UIGestureRecognizer and then implement this method:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
return [[touch view] isEqual:UIViewThatIsNotYourButton];
}
Your hitTest method should actually achieve this, so you might also check if the UIButton is really getting an action assigned to it.