Adding a UIPanGestureRecognizer and a UISwipeGestureRecognizer to same view causes conflicts after setting requireGestureToFail

StackOverflow https://stackoverflow.com/questions/21723932

Question

I added a swipe gesture recognizer and a pan gesture recognizer to the same view. These gestures should be exclusive to each other.

In order to do this I added the constraint on the swipe gesture

[swipeGesture requireGestureToFail:panGesture];

(because the pan gesture should get precedence)

Problem is that the pan gesture is always invoked - even during a very fast swipe.

In order to over come this I set myself as the pan gesture's delegate. In the delegate method I set up some code as follows:

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
    // check if it is the relevant view
    if (gestureRecognizer.view == self.myViewWithTwoGestures)
    {
        // check that it is the pan gesture
        if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]])
        {
            UIPanGestureRecognizer *pan = (UIPanGestureRecognizer *)gestureRecognizer;
            CGPoint velocity = [pan velocityInView:gestureRecognizer.view];
            // added an arbitrary velocity for failure
            if (ABS(velocity.y) > 100)
            {
                // fail if the swipe was fast enough - this should allow the swipe gesture to be invoked
                return NO;
            }
        }
    }
    return YES;
}

Is there a suggested velocity to ensure good behavior? Is there another way to force the pan gesture to fail?

Was it helpful?

Solution

According to Apple's documentation here (under Declaring a Specific Order for Two Gesture Recognizers) the way to get both UIPanGestureRecognizer and UISwipeGestureRecognizer to work on the same view is by requiring the UISwipeGesureRecognizer to fail before calling the UIPanGestureRecognizer (the opposite of what you wrote). This probably has something to do with the fact the a swipe gesture is also a pan gesture but the opposite is not necessarily true (see this SO question).

I wrote this little piece of code and it manages to recognize both pan and swipe gestures:

UIPanGestureRecognizer * pan = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(panned:)];
UISwipeGestureRecognizer * swipe = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(swiped:)];
[pan requireGestureRecognizerToFail:swipe];
swipe.direction = (UISwipeGestureRecognizerDirectionLeft | UISwipeGestureRecognizerDirectionRight);

-(void)panned:(UIPanGestureRecognizer *)gesture
{
    NSLog(@"Pan");
}

-(void)swiped:(UISwipeGestureRecognizer *)gesture
{
    NSLog(@"Swipe");
}

This doesn't work as well as you'd hope (since you need the swipe gesture to fail there's a small delay before the pan gesture starts) but it does work. The code you posted however gives you the ability to fine tune the gestures to your liking.

OTHER TIPS

Late response, but I was having a similar issue where I wanted to pan to be recognized before the swipe. The only way I could get it working was to use a long press (or something similar) to set a flag to use the pan gesture as a pan or a swipe. I ended up not using swipes at all. I.e.:

- (void) handleLongPress : (UILongPressGestureRecognizer *) gestureRecognizer
{
    if (gestureRecognizer.state == UIGestureRecognizerStateBegan)
    {
        _canSwipe = YES;
    }
    else if (gestureRecognizer.state == UIGestureRecognizerStateEnded)
    {
        _canSwipe = NO;
    }
}

- (void) handleDragging : (id) sender
{
    UIPanGestureRecognizer *pan = (UIPanGestureRecognizer *)sender;
    GLKVector2 dragDelta = GLKVector2Make(0., 0.);

    if (pan.state == UIGestureRecognizerStateBegan || pan.state == UIGestureRecognizerStateChanged)
    {
        _mousePosition = [pan translationInView:self.view];
        if (_beginDragging == NO)
        {
            _beginDragging = YES;
        }
        else
        {
            dragDelta = GLKVector2Make(_mousePosition.x - _prevMousePosition.x, _mousePosition.y - _prevMousePosition.y);
        }

        _prevMousePosition = _mousePosition;
    }
    else
    {
        _beginDragging = NO;
    }

    if (_canSwipe == YES)
    {
        if (dragDelta.x > 0)
        {
            _canSwipe = NO;
            [self.navigationController popToRootViewControllerAnimated:YES];
            NSLog(@"swipe right");
        }
        else if (dragDelta.x < 0)
        {
            _canSwipe = NO;
            [self performSegueWithIdentifier:@"toTableSegue" sender:pan];
            NSLog(@"swipe left");
        }
    }
    else
    {
        _dragDeltaTranslation = GLKVector2Make(dragDelta.x/90, dragDelta.y/90);
        _translationXY = GLKVector2Make(_translationXY.x + _dragDeltaTranslation.x, _translationXY.y - _dragDeltaTranslation.y);
    }
}

So essentially:

  1. Use long press (or some other mechanism) to activate a state of swiping (long press is nice because as soon as you release, the state goes to UIGestureRecognizerStateEnded)
  2. Then use the pan direction to determine the direction of the swipe. 2.
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top