28

I'm trying to recognize left/right swipe gesture in a UIScrollView. I've tried to create UISwipeGestureRecognizers and associate them with the scroll view. It works but very rarely. Most of the time I do not get called. Why?

How can I reliably get swiping left/right to work? Can I use the gesture recognizers or do I have to somehow handle it myself in touchesBegan/Ended

Thanks

DShah
  • 9,768
  • 11
  • 71
  • 127
David
  • 2,770
  • 5
  • 35
  • 43

4 Answers4

42

Figured it out. In my case, my UIScrollView contained a UIImage that I allowed zooming. Apparently that meant that scrolling is enabled and the UIScrollView had trouble distinguishing between gestures intended to scroll vs. swipe (next, previous image).

The key in my case, is to disable scrolling in the scroll view when the image is not zoomed in, and renabled it when it is zoomed in. This provides the expected behavior.

The critical piece is to put the following in the scroll view's delegate:

- (void)scrollViewDidZoom:(UIScrollView *)scrollView {
  if (scrollView.zoomScale!=1.0) {
    // Zooming, enable scrolling
    scrollView.scrollEnabled = TRUE;
  } else {
    // Not zoomed, disable scrolling so gestures get used instead
    scrollView.scrollEnabled = FALSE;
  }
}

I also have to initialize the scroll view with scrolling disabled. To enable zooming, simply provide an image on a delegate call,

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView {
  // Return the scroll view
  return myImage;
}

And set a few parms in viewDidLoad for the zooming and setup gesture recognizers as well

- (void)viewDidLoad {
  [super viewDidLoad];
  myScrollView.contentSize = CGSizeMake(myImage.frame.size.width, myImage.frame.size.height);
  myScrollView.maximumZoomScale = 4.0;
  myScrollView.minimumZoomScale = 1.0;
  myScrollView.clipsToBounds = YES;
  myScrollView.delegate = self;

  [myScrollView addSubview:myImage];
  [self setWantsFullScreenLayout:TRUE];

  myScrollView.scrollEnabled = FALSE; 
  UISwipeGestureRecognizer *recognizer = 
    [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(handleSwipeFrom:)];
  recognizer.delaysTouchesBegan = TRUE;
  [myScrollView addGestureRecognizer:recognizer];
  [recognizer release];

  recognizer = [[UISwipeGestureRecognizer alloc] initWithTarget:self action:@selector(handleSwipeFrom:)];
  recognizer.direction = UISwipeGestureRecognizerDirectionLeft;
  [myScrollView addGestureRecognizer:recognizer];
  [recognizer release];
  [myScrollView delaysContentTouches];
}
Dan J
  • 25,433
  • 17
  • 100
  • 173
David
  • 2,770
  • 5
  • 35
  • 43
  • 1
    really excellent find David. my swipe recognizers also failed to work.i gave up and used touchesBegan: with NSNotification, but it gave notification for MasterViewControllers also(in iPad).i was blurry-eyed of searching for that prob. You saved my time n energy!! heartfelt thanks David. Keep posting tricks.. have a great day. – gopikrishnan Dec 08 '10 at 21:56
  • Or maybe in one line - scrollView.scrollEnabled = (scale != 1.0f) – Yariv Nissim Feb 22 '13 at 09:11
  • Thank you! I would make a slight suggestion as I was running into issues when in landscape. I bypassed those issues by changing the scrollViewDidZoom method to this: '- (void)scrollViewDidZoom:(UIScrollView *)scrollView { if (scrollView.zoomScale!=self.scrollview.minimumZoomScale) { // Zooming, enable scrolling scrollView.scrollEnabled = TRUE; } else { // Not zoomed, disable scrolling so gestures get used instead scrollView.scrollEnabled = FALSE; } }' – johnnelm9r Jul 22 '13 at 08:07
  • 1
    New answer below using requireGestureRecognizerToFail is much easier. – Daniel Mar 16 '15 at 04:17
36
UIScrollView *scrollView = ...
UISwipeGestureRecognizer *mySwipe = ...

The correct solution to fix this issue is to add one line of code:

[scrollView.panGestureRecognizer requireGestureRecognizerToFail:mySwipe]

Swift version:

scrollView.panGestureRecognizer.requireGestureRecognizerToFail(mySwipe)

Swift4 version:

scrollView.panGestureRecognizer.require(toFail: mySwipe!);
Borzh
  • 5,069
  • 2
  • 48
  • 64
Alexander Volkov
  • 7,904
  • 1
  • 47
  • 44
  • Definitely agree that this is the correct solution! I also has to add in the same line for the `pinchGestureRecognizer` to get it to be reliable as I was trying to detect a multi-finger swipe. – Luke Rogers Apr 24 '15 at 10:51
  • This definitely seems like the right way to go! Sometimes I find the swipe gesture fires a bit too soon, but that's now a swipe handler issue. At least we don't have to do all sorts of weird hacky things that other people suggest, and just "Feel wrong". Thanks!! – horseshoe7 Dec 14 '15 at 16:24
  • 1
    great Answer of all time.. :) – Ankit Kumar Feb 11 '16 at 12:48
  • 1
    Doesn't this simply break the pan gesture of the scroll view in the direction of the swipe gesture? At the very least, I can't seem to get the swipe gesture to fail. What if you want the ability to pan a zoomed-in image, but swipe between images as well? Seems like the accepted solution is better, even though it's maybe "hacky". – nickjwallin Jun 06 '16 at 19:45
  • @nickjwallin nope. – Alexander Volkov Jun 09 '16 at 13:59
4

Good post.

I was doing a similar thing (no image view) and I basically had to disable scrolling if the contentSize was smaller than the height (my scroll view only scrolls vertical).

if (scrollView.contentSize.height>scrollView.frame.size.height) {
    scrollView.scrollEnabled = YES;
}
else {
    scrollView.scrollEnabled = NO;
}

That did the trick for me

echappy
  • 533
  • 5
  • 13
1

For those who want to animate and customize their swipe gesture recognizers.

We can use UIScrollView and UIGestureRecognizer delegates:

 Class ViewController: UIViewController, UISCrollViewDelegate, UIGestureRecognizerDelegate { 


   override func viewDidLoad() {
    super.viewDidLoad()

    scrollView.delegate = self
    swipeLeft.delegate = self
    swipeRight.delegate = self

  }


  func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    return true
  }

  func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
    return true
  }

  func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
    return scrollView.alwaysBounceHorizontal
  }


  func scrollViewWillEndDragging(_ scrollView: UIScrollView, withVelocity velocity: CGPoint, targetContentOffset: UnsafeMutablePointer<CGPoint>) {

    // Your custom animation at the end of scrolling.
  }
}
Matthew Kerian
  • 812
  • 5
  • 18
Celeste
  • 1,519
  • 6
  • 19