UIPanGestureRecognizer top level view not getting events, subviews consuming them - ios

Using UIPanGestureRecognizer & UITapGestureRecognizer on top-level UIView.
The setup in Interface Builder:
ViewController (our main view controller)
UIView (our main view and wired to our UIViewController
Our core UIView has a subview called a “Block” which is simply a UIView.
The Block view has 4 subviews (children) each being an instance of a UIButton.
The UIButton has its Touch Up Inside event wired to the the UIViewController.
The UIView (our main top-level UIView) has a UIPanGestureRecognizer and a UITapGestureRecognizer
Here is the scenario we are trying to accomplish (a.k.a.The behavior):
A user taps a button (a cell).
The button will change its stated from “normal” to “selected”. (This works fine and the code is simple)
With a selected item, a user can place their finger anywhere on the screen an move it up or down
The issue:
Need to know when panning stops.
The top UIView does not receive a gesture stated of ended.
The UIView does not receive a touchesEnded event.
How do you know when the user has lifted their finger? Suppose I start the panning when my finger is over a UIButton, while panning occurs, the UIButton eats the touches begin and end events. Therefore, you have no way of knowing when the user stopped moving their finger across the iPhone/iPad glass.

First implement UIGestureRecognizerDelegate in your view controller.
Then set the delegate on your gesture recognizers to self and implement the following method
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
Your target method should look like this:
-(void)gestureRegognized:(UIGestureRecognizer*)gestureRecognizer
{
if ([gestureRecognizer isMemberOfClass:[UIPanGestureRecognizer class]])
{
//check its state
if(gestureRecognizer.state==UIGestureRecognizerStateBegan)
{
// add your code here
}
else if(gestureRecognizer.state==UIGestureRecognizerStateEnded)
{
// pan gesture ended code goes here
}
}
else if([gestureRecognizer isMemberOfClass:[UITapGestureRecognizer class]])
{
if(gestureRecognizer.state!=UIGestureRecognizerStateFailed)
{
// tap gesture detected
}
}
}

Related

UITapGestureRecognizer on UIView and Its Subview Respond Together When Subview Is Tapped

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

UIView handle and pass touch events to next sibling view

I'm building an iOS app that has a custom UIView upon a UIScrollView which in turn has a subview.
Here's the layout structure:
Note that the custom UIView(called "Detected Object Hint View") is not a subview of ScrollView, it's a sibling view of UIScrollView. And I want to respond to tap gesture on the custom UIView, so I've added UITapGestureRecognizer to the UIView, and it works for tap, but the UIScrollView will never get any touch events (not responding to scroll or zoom gesture).
I've googled a while, and a lot of people pointed out that in order for other view to respond to the touch events, I should implement the following method:
- (id)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
UIView *hitView = [super hitTest:point withEvent:event];
if (hitView == self){
return nil;
}
else {
return hitView;
}
}
But once I've added this method to my custom UIView, it will not respond to tap gesture either (of course).
So I'm wondering how can I handle the tap gesture on my custom UIView and pass the touch events to UIScrollView as well?
Big thanks!

enable swipe on view controller, but disable within a view in that VC

I have a UIViewController where I have 2 UISwipeGestureRecognizers handling undo and redo (swipe right to undo, swiple left to redo). Within this VC I also have a UIView that is tracking touch began/moved/ended to change colors on another UIView. BTW, the undo/redo has to do with the color changes.
The problem I'm running into is that when I'm doing the touch events in the color changer view, they are sometimes interpreted as a swipe and undo/redo are happening.
How can I disable the swipe gestures just for the UIView in question but retain the ability to perform swipes on other areas of the VC?
Make the view controller a delegate of the gesture recognizer, then implement this delegate method:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
return touch.view != self.mySubviewToExclude;
}

Shouldn't UIRecognizers fire when a subview is touched?

Views in my iPad app behave as if they prevent their superview's gesture recognizers from firing when the user initiates such gesture in that view.
Is this expected?
How can I remove that shielding behavior?
What are good practices to debug gesture recognizers?
In more details:
The main "canvas" view of my application, lets the user adds shapes to it with a "long double tap". I attached a gesture recognizer for such gestures to the main view. That works very well: the main view gets called, and reacts by adding a shape to the main view.
Shapes are implemented as subviews of the main view. When the user long-double-taps in the main view, my code instanciate a shape subview, and adds it to the main view. Shape views can be moved around with a long-single-tap recognizer. So I also attach a gesture recognizer for long-single-taps to every shape view. That works very well: the shape view gets called and lets the user move it in the canvas.
However, when the user long-double-taps in a shape view, nothing happens: the shape view is not called, which is expected since it doesn't have a gesture recognizer for long-double-taps. But the main view is not called either. I had thought that since the gesture was not recognized by the shape view, then it would be propagated up in the responder chain to the main view. But this doesn't happen.
My intent is to let the user add overlapping shapes to the main view, so that a long-double-tap on a shape would also add a new shape to the main view.
What could I have missed?
I can of course add a long-double-tap recognizer to shape views, and from there, either forward the gesture to the main view or handle the gesture directly in a way similar to what I do in the main view.
But this sounds wasteful, and more importantly, I'd like to understand the behavior.
Thanks for any suggestion.
It should as far as I can see pass the message along out of the box.
To ensure both gestureRecognizers are not fired you need to do something like:
[longPress requireGestureRecognizerToFail:doubleLongPress];
Update
Just free styling here but if you want to limit the gesture to one view you could try playing with the gesture delegate (this will only respond if the touched view is self.view)
self.myGesture.delegate = self;
In your controller do something like:
//.h
#interface MyController : UIViewController <UIGestureRecognizerDelegate>
// ...
#end
//.m
#implementation MyController
//...
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch;
{
BOOL shouldReceiveTouch = YES;
if (gestureRecognizer == self.myGesture) {
shouldReceiveTouch = (touch.view == self.view);
}
return shouldReceiveTouch;
}
//...
#end
NB I haven't tested this but I will update when I test it later.

Detecting swipe gestures on UITableViewCell inside UIScrollView

I am hoping someone will be able to help me with a problem that is doing my head in at the moment!
Given the following view hierarchy
I want to be able to detect swipe gestures on my custom UITableViewCell.
I have subclassed the UIScrollView and have a hitTest:withEvent: method that checks whether I am touching the tableview cell (or its content) or not, in which case I set the following scroll view properties:
- (UIView*)hitTest:(CGPoint)point withEvent:(UIEvent *)event
{
UIView* result = [super hitTest:point withEvent:event];
if ([result.superview isKindOfClass:[UITableViewCell class]] || [result.superview tag] == SUBVIEW_TAG)
{
self.canCancelContentTouches = NO;
self.delaysContentTouches = YES;
} else {
self.canCancelContentTouches = YES;
self.delaysContentTouches = NO;
}
return result;
}
I have also implemented:
- (BOOL)touchesShouldCancelInContentView:(UIView *)view
{
if (view.tag == SUBVIEW_TAG || [[view superview] isKindOfClass:[UITableViewCell class]])
return NO;
return YES;
}
And am returning NO in case the view being touched is the table view cell.
These methods are all getting called and performing their actions as expected, but I am still unable to stop the UIScrollView from "hogging" the swipe gesture.
The interesting thing is that if I include the UIView that contains the tableview and cell on both of the methods above (the one with SUBVIEW_TAG) it works perfectly so I am guessing it must be something to do with the fact that UITableView inherits from UIScrollView.
My main goal is to be able to swipe on the cell to reveal more options for the cell. A horizontal swipe anywhere else on that view would be captured by the scroll view and shift the content horizontally as per its normal behaviour.
Any ideas would be very much appreciated!
Thanks!
Rog
I had a similar problem with a swipe detect for a component inside a scrollview and I was able to resolve it with
[scrollView.panGestureRecognizer requireGestureRecognizerToFail:swipeGesture]
Where scrollView is the scroll view object that acts like container and swipeGesture is the component swipe gesture object inside scrollview.
So, you can define a swipe for the cell object like this (for right swipe in the example, custom it as you want)
UISwipeGestureRecognizer* rightSwipeRecognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:#selector(yourMethod)];
[rightSwipeRecognizer setDirection:UISwipeGestureRecognizerDirectionLeft];
[cell addGestureRecognizer:rightSwipeRecognizer];
and then do
[scrollView.panGestureRecognizer requireGestureRecognizerToFail:rightSwipeRecognizer]
The documentation of requireGestureRecognizerToFail says:
This method creates a relationship with another gesture recognizer
that delays the receiver’s transition out of
UIGestureRecognizerStatePossible. The state that the receiver
transitions to depends on what happens with otherGestureRecognizer:
If otherGestureRecognizer transitions to
UIGestureRecognizerStateFailed, the receiver transitions to its normal
next state.
if otherGestureRecognizer transitions to
UIGestureRecognizerStateRecognized or UIGestureRecognizerStateBegan,
the receiver transitions to UIGestureRecognizerStateFailed.
An example where this method might be called is when you want a
single-tap gesture require that a double-tap gesture fail.
Availability Available in iOS 3.2 and later.
Hope helps!
The solution is pretty simple. All you need to do is add UIScrollView inside you UITableViewCell. It will prevent "hogging" effect during swipe gesture.

Resources