UIPanGestureRecognizer conflicts with UIPinchGestureRecognizer's superview - ios

I have a UIScrollView instance with a subview that has its own UIPanGestureRecognizer which is used to move the subview inside the scrollview. I would like the pinch-to-zoom feature of the scrollview to have the top priority over the pan gesture. However, this is not the case: when starting to pinch with one finger over my subview, it will pan instead.
What I tried so far:
gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer:: it will pan AND pinch
[panGestureRecognizer requireGestureRecognizerToFail:scrollView.pinchGestureRecognizer];: panning just won't work anymore (callback is triggered only for UIGestureRecognizerStateEnded state). And panGestureRecognizer.cancelsTouchesInView = NO won't help.
My understanding is that when starting to pinch from the subview, both the scroll view and the subview only receive one touch each, that's why in the second case the pinch gesture recognizer doesn't even fail because it doesn't even begin to handle the event.
So, any idea on how to achieve this?

One way to do it is to allow simultaneous interaction with the scrollView.pinchGestureRecognizer, and then cancel the panGestureRecognizer if the scrollView.pinchGestureRecognizer is actually zooming. The only way I know how to cancel them is to disable/enable.
-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
if (gestureRecognizer == panGestureRecognizer){
if (otherGestureRecognizer == scrollView.panGestureRecognizer) {
return 0;
}
else if(otherGestureRecognizer == scrollView.pinchGestureRecognizer){
if (scrollView.pinchGestureRecognizer.scale != 1) {
gestureRecognizer.enabled = 0;
gestureRecognizer.enabled = 1;
}
}
}
return 1;
}

Related

ios swipe after longpress is ended, without lifting the finger

I want to implement back navigation, using longpress and swipe to the left, without lifting the finger, but the swipe gesture isn't recognised, if I don't lift the finger after the longpress.
I also implemented the following delegate method, but the desired result isn't appearing. Any thoughts?
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
if (gestureRecognizer == _longPress && otherGestureRecognizer == _swipe) {
return YES;
}
if (gestureRecognizer == _swipe && otherGestureRecognizer == _longPress) {
return YES;
}
return NO;
edit:
- the longpress gesture fires method, which changes the background color of the current UIViewController (made it, just to see, if it fires).
-the swipe gesture fires method, -popViewController:animated
Don't use 2 different gesture recognisers because this is 1 gesture. You should create a custom gesture subclass to encode your logic so it's a single logical gesture for you to add and for the user to execute.
Inside your gesture i'd have a small state machine so you know when you start, when the long press time is up, if they have actually swiped enough. From each state you're only looking for one thing to happen, if anything else happens then you know it's a failure and the gesture can fail out.

UIPinchGestureRecognizer Disable Pinch Out

I have added a pinch gesture recognizer to a scroll view, using it to close a modal view controller. I did it like so:
UIPinchGestureRecognizer *closePinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(closeGallery)];
[galleryScrollView addGestureRecognizer:closePinch];
Although it is added to a scrollView, I do not actually use it to zoom only to close the view. Therefore, I have no need for the pinch-out gesture as it indicates zooming in.
Is there a way to easily disable the pinch-out portion of the gesture recognizer and leave the pinch-in untouched?
Based on Crazyrems' answer, the following delegate method did exactly what I needed:
- (BOOL)gestureRecognizerShouldBegin:(UIPinchGestureRecognizer *)gestureRecognizer
{
// Negative velocity indicates pinch out
if (gestureRecognizer.velocity < 0) {
return YES; // <- Register touch event
} else {
return NO; // <- Do not register touch event
}
}
You should implement -gestureRecognizerShouldBegin: in your UIGestureRecognizerDelegate
There's a velocity property in the recognizer passed in parameter, so you can check if it's a pinch in or out, and return YES or NO in consequence.

disable UIScrollView dragging event to specific direction and send the event to super

I have UIView and I attached to it a UIPanGestureRecognizer.
inside the UIView I have UIScrollView with paging enabled and I set the content size so scrollView could be scrolled just to the left.
the problem:
I want when user try to drag scrollView To the right, to send the event up to UIView so 'UIPanGestureRecognizer' can handle the touch event
at last, after 6 hours I figure it out
I subclassed UIScrollView and implemented gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer delegate method
when user drag over the scrollView this method get called (BOOL)gestureRecognizer:shouldRecognizeSimultaneouslyWithGestureRecognizer and by default it returns NO
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
if (self.contentOffset.x > 0 && self.contentOffset.x <= self.contentSize.width - self.frame.size.width) {
//user is in middle of dragging the scrollView don't allow any other gestureRecognizer to respond
return NO;
}else{
//scrollView contentOffset is 0 and user is not dragging the scrollView so let other gestureRecognizer to respond
return YES;
}
}
In trying to use homam's answer, I realized that the scrollView.panGestureRecognizer was being cut off after a return NO. What I wanted was for my custom pan recognizer to be cut off and the scrollView to still scroll. I think that might've been the result of the gestures being added at different levels, I'm not sure.
I found a solution, however, by changing my custom gesture recognizer to check for the scrollView.contentOffset and not activate unless it was greater than zero. I also turned off the scrollView.bounce property once the scrollView.contentOffset got to zero. When scrolling down on my scrollView it would hit the end and immediately my gesture recognizer would take over and move the superview out of the way like I wanted.
Here's a skeleton of my custom pan gesture
- (void)customPan:(UIPanGestureRecognizer*)panRecognizer
{
if(panRecognizer.state == UIGestureRecognizerStateBegan)
{
//do begin stuff
}
if(panRecognizer.state == UIGestureRecognizerStateChanged)
{
if(self.scrollView.contentOffset.y > 0)
{
//do begin stuff to make sure it’s initialized properly when it does start to change
self.scrollView.bounces = YES;
return;
}
else
{
self.scrollView.bounces = NO;
}
//do change stuff
}
if(panRecognizer.state == UIGestureRecognizerStateEnded)
{
if(self.scrollView.contentOffset.y > 0)
{
//do begin stuff to make sure it’s initialized properly when it does start to change
self.scrollView.bounces = YES;
return;
}
else
{
self.scrollView.bounces = NO;
}
//do end stuff
}
}
[myScrollView setContentSize:CGSizeZero];

UIScrollView overrides my subview's pan gesture recognizers

If I have a scrollView with a subview and the subview has a pan gesture recognizer, the scrollView's pan gesture override's the subview's pan. What I want is the opposite, I think, so that is I drag a subview it will pan within the scroll view, yet if I touch another area the scroll view will pan as normal. Is there an easy way to set that up?
Here's what works for me:
UIPanGestureRecognizer *subviewPanRecognizer = [[UIPanGestureRecognizer alloc]
initWithTarget:self action:#selector(panSubview:)];
[subview addGestureRecognizer:subviewPanRecognizer];
// play nice with subview's pan gesture
[scrollView.panGestureRecognizer
requireGestureRecognizerToFail:subviewPanRecognizer];
Set canCancelContentTouches property of UIScrollView to false if you don't want to scroll on touching subviews.
Original answer
Overwrite these two delegate below,
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
This will allow you to recognize both gestures, the default return is NO, so we need to overwrite it and return YES.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
if ([gestureRecognizer isKindOfClass:[UITapGestureRecognizer class]]) {
if ([otherGestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) {
return NO;
}else{
return YES;
}
}
return YES;
}
In this delegate you can do anything as you wish, as it's name the gestureRecoginzer will be required to fail by the otherGestureRecognizer, all you need to do is to judge what kind of these two gestures and return YES or NO.

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