9
votes

I'm trying to add a pan gesture recognizer to a view containing a scrollview, but I guess I've problems with priorities.

My global UIView has a UIPanGestureRecognizer set like this:

_bottomPanGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(bottomPanGestureDetected:)];
_bottomPanGestureRecognizer.minimumNumberOfTouches = 2;
_bottomPanGestureRecognizer.maximumNumberOfTouches = 2;
_bottomPanGestureRecognizer.delaysTouchesBegan = NO;
_bottomPanGestureRecognizer.delaysTouchesEnded = NO;

I want to recognize this gesture to display another view from the bottom with some sort of pinch down-to-up.

The problem is that the scrollview is recognizing its own pan gesture before mine.

So I tried to delay it thanks to:

[_scrollView.panGestureRecognizer requireGestureRecognizerToFail:_bottomPanGestureRecognizer];

And it's working, the scrollview event is fired after my two finger down to up recognizer, but the problem is now when I only use one finger to scroll in the scrollview, the scroll works after a small delay.

I would like to have no delay for this event, is this possible? Any idea welcomed!

Cheers.

Cyril

3
Have you tried to set maximumNumberOfTouches of _scrollView.panGestureRecognizer to 1?kovpas
Yes but strangely, it appears that this condition is ignored.cyrilPA
Well, another option is to implement UIGestureRecognizerDelegate's gestureRecognizerShouldBegin: and check number of touches there. So if it's two touches, return NOkovpas
Not working because of this: "UIScrollView's built-in pan gesture recognizer must have its scroll view as its delegate.". It seems to be impossible to override scrollview gesture recognizer delegate. Any other idea?cyrilPA
Did you found a solution for this? i'm experiencing the same problemAndy Jacobs

3 Answers

7
votes

In case it isn't solved yet, i solved the problem for me.

I added a UIPanGestureRecognizer to a UIScrollView to detect two finger pan gestures and the default UIScrollView behaviour (scrolling to something) still workes.

So what i did is to add the UIPanGestureReconizer to the UIScrollView:

UIPanGestureRecognizer *pangestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(displayReloadIndicator:)];
pangestureRecognizer.minimumNumberOfTouches = 2;
pangestureRecognizer.delegate = self;
[self.scrollView addGestureRecognizer:pangestureRecognizer];
[pangestureRecognizer release];

After this i added the code:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {

    return YES;
}

After this, i implemented the pan gesture recognizers action method.

- (void) displayReloadIndicator:(UIPanGestureRecognizer*) panGestureRecognizer {

    UIGestureRecognizerState gestureRecognizerState = gestureRecognizer.state;

    CGPoint translation = [gestureRecognizer translationInView:self.scv_bibgesamt];

    if (gestureRecognizerState == UIGestureRecognizerStateBegan) {

        // create a UIView with all the Pull Refresh Headers and add to UIScrollView
        // This is really much lines of code, but its simply creating a UIView (later you'll find a myRefreshHeaderView, which is my base view) and add UIElements e.g. UIActivityIndicatorView, a UILabel and a UIImageView on it
        // In iOS 6 you will also have the possibility to add a UIRefreshControl to your UIScrollView

    }

    else if (gestureRecognizerState == UIGestureRecognizerStateEnded
             || gestureRecognizerState == UIGestureRecognizerStateCancelled) {

        if (translation.y >= _myRefreshHeaderView.frame.size.height + 12) { // _myRefreshHeaderView is my baseview

         //so the UIScrollView has been dragged down with two fingers over a specific point and have been release now, so we can refresh the content on the UIScrollView
         [self refreshContent];

         //animatly display the refresh view as the top content of the UIScrollView
         [self.scrollView setContentOffset:CGPointMake(0, myRefreshHeaderView.frame.size.height) animated:YES];
    }

    else {

         //the UIScrollView has not been dragged over a specific point so don't do anything (just scroll back to origin)
         [self.scrollView setContentOffset:CGPointMake(0, 0) animated:YES];

         //remove the view (because it's no longer needed)
         [_myRefreshHeaderView removeFromSuperview];
    }
}

UPDATE:

In case you may wish to integrate the swipe back functionality from your navigationcontroller, you should integrate following code:

- (void) viewDidLoad {

    [super viewDidLoad];


    if ([self.navigationController respondsToSelector:@selector(interactivePopGestureRecognizer)]) {

        self.navigationController.interactivePopGestureRecognizer.enabled = YES;

        self.navigationController.interactivePopGestureRecognizer.delegate = nil;
    }

    //setup view controller
}

and

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {

    if (gestureRecognizer == _panGestureRecognizer
        && [self.navigationController.viewControllers count] > 1) {

        CGPoint point = [touch locationInView:self.view.window];

        if (point.x < 20
            || point.x > self.view.window.frame.size.width - 20) {

            return NO;
        }
    }

    return YES;
} 
6
votes

Implement panRecognizer delegate to enable simultaneously recognize UIScrollView UIGestureRecognizer

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    if (_panRecognizer == gestureRecognizer) {
        if ([otherGestureRecognizer.view isKindOfClass:UIScrollView.class]) {
            UIScrollView *scrollView = (UIScrollView *)otherGestureRecognizer.view;
            if (scrollView.contentOffset.x == 0) {
                return YES;
            }
        }
    }

    return NO;
}
0
votes

SWIFT 4

If using a scrollView you can use below, it is only a panGestureRecognizer when a scrollView is dragging at the very top:

translation.y > 0 means you are moving from top to bottom and locationInScrollView.y < 500.0 means you end dragging at 500 or less (you can customize this) to prevent that the refresh is done in middle or bottom of the scroll.

func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {

    let translation = scrollView.panGestureRecognizer.translation(in: scrollView.superview)
    let locationInScrollView = scrollView.panGestureRecognizer.location(in: scrollView)

    if translation.y > 0 && locationInScrollView.y < 500.0 {
        print("scrollView refresh:  Y: \(locationInScrollView.y)")
        setupUI()
    }
}