4

I have an UIScrollView inside another UIScrollView. The outer scroll view handles paging, the inner scroll view handles zooming (it’s an image gallery with zooming support). I need the inner scroll view to ignore single touches so that they go to the outer scroll view instead.

This can be done using [innerScrollView setUserInteractionEnabled:NO], but that obviously also turns off the pinch and panning gestures on the inner scroll view. Is there a way to keep the pinch and pan gestures handled by the inner zoom view and forward all other events up the responder chain?

PS. I can always come with some delegation hack to solve the issue. I’d like to know if there’s a simple way to get it working using the regular responder chain.

zoul
  • 102,279
  • 44
  • 260
  • 354

2 Answers2

3

What I have found out: The single-tap event eventually makes its way to touchesBegan:withEvent: on the content view inside the inner scroll view. This content view does not implement the method and the default UIView implementation passes the event up the responder chain. Here we have the inner scroll view, which does implement touchesBegan:withEvent:. This implementation swallows the single tap and nothing goes up the responder chain. What helped is to subclass UIScrollView and send the touches up the responder chain manually:

- (void) touchesBegan: (NSSet*) touches withEvent: (UIEvent*) event
{
    [[self nextResponder] touchesBegan:touches withEvent:event];
}

Of course it’s also good to forward at least touchesEnded:withEvent: this way. This does everything I need. The inner scroll view can handle zooming and panning, the outer scroll view receives the single-tap events. I can even zoom in and then pan so far to the right that I go to the next page.

zoul
  • 102,279
  • 44
  • 260
  • 354
  • Good question and great answer. I'm using this to add a `UIScrollView` inside a `UITableViewCell` to mimic the "swipe for actions". I needed to ignore tap events on the scrollView and get them forwarded to the cell itself to keep the tableCell selection with `didSelectRowAtIndex` working. – yonel Jan 29 '14 at 22:04
2

On iOS 5, UIScrollViews expose their panGestureRecognizer and pinchGestureRecognizer.

So, if you don't need to support older iOS versions, you could try innerScrollView.panGestureRecognizer.minimumNumberOfTouches = 2.

EDIT: This does not work; the scrollView still swallows the touches. I'll leave this here for further reference.

If you don't want the inner scrollView to handle panning at all, you could probably (I have not tried it) just remove its panGestureRecognizer.

fzwo
  • 9,842
  • 3
  • 37
  • 57
  • I want it to handle panning, even panning with a single finger. I just don’t want it to respond to single-touch events, to swallow them. Which it does, even after raising the minimum number of touches on the pan gesture recognizer. – zoul Jun 13 '12 at 08:41
  • Isn't panning with a single finger a single-touch event? I'm afraid I seem to misunderstand you. BTW, if you do come up with a good solution, I would be interested in hearing it. I find ScrollView hacks weirdly fascinating. – fzwo Jun 13 '12 at 08:43
  • @zoul Also, how do you, from a user interface perspective, want to handle horizontal panning and paging? Pan until the end is reached, then page? – fzwo Jun 13 '12 at 08:47
  • Sorry, I’m probably being too careless with the lingo. What I want the inner scroll view to ignore is when user taps the screen and raises the finger without moving it (a “click”, not a “drag”). I have a working solution, I’m currently editing my answer. – zoul Jun 13 '12 at 08:48
  • @zoul This is called a "Tap". I think the easiest solution would be to create a `UITapGestureRecognizer` and attach that to the inner ScrollView: `[innerScrollView addTapGestureRecognizer:myTGR]`. This TapGestureRecognizer would then call a method in your controller that handles the tap. It's not ignoring, but it's probably easier to do. – fzwo Jun 13 '12 at 09:03
  • 2
    I’m so particular about *ignoring* the tap because I want to keep the loose coupling between the “content” controller and the controller that implements the paging. With the gesture recognizer there would have to be an extra interface for the content controller to report the taps higher. (Which is what the responder chain is already there for.) Thank you for the discussion! – zoul Jun 13 '12 at 09:15
  • Interestingly enough I subclassed UIScrollView, set itself to its own delegate, added self.panGestureRecognizer.minimumNumberOfTouches = 2 and the other appropriate methods for zooming, shoved it into another UIScrollView and your solution worked for me. – David Zorychta May 18 '13 at 18:45