Paging UIScrollView with two-finger pan and zoom
Asked Answered
B

3

8

The test case is easily reproduced: download Apple's PhotoScroller sample app, and try to adapt it so that panning (both around a zoomed image, and between each image) only works with two fingers.

Setting the panGestureRecognizer for both the pagingScrollView and the imageScrollView to only accept a min & max of 2 touches seems like a good place to start, however it doesn't work. It lets you scroll around an image with two fingers just fine*, however paging then doesn't work.

I've tried so many combinations of settings and custom gesture recognizers, and I'm a bit stumped. Is a custom scroll view subclass going to be of any use, or can I somehow manipulate the scroll view delegate methods to make it work?

*EDIT: Actually, it doesn't scroll fine in this situation. The view no longer glides smoothly as with a single touch...

UPDATE: I'm still struggling with this one. I would appreciate some input from somebody who has played around with UIGestureRecognizers and UIScrollViews.

EDIT:

Setting the ImageScrollView class to only accept two touches:

- (id)initWithFrame:(CGRect)frame
{
    // ...
    // Template code
    // ...

    [self.panGestureRecognizer setMinimumNumberOfTouches:2];
    [self.panGestureRecognizer setMaximumNumberOfTouches:2];
}

Setting PhotoViewController's pagingScrollView to only accept two touches:

- (void)loadView
{
    // ...
    // Template code
    // ...

    [pagingScrollView.panGestureRecognizer setMinimumNumberOfTouches:2];
    [pagingScrollView.panGestureRecognizer setMaximumNumberOfTouches:2];
}

These modifications are made directly on top of the PhotoScroller sample app. I would expect these simple changes to work for two-finger interaction, however the side-effects are odd (as explained above).

Blackman answered 6/11, 2011 at 3:43 Comment(5)
@PragmaOnce: Yeah, sorry to link such a large sample. It contains all the full size and tiled images for the paging scroll view. Pretty pictures aren't necessary to demonstrate my point, but hey...!Blackman
Could you care to post your code so that we may help you.Gassy
The full code for implementing the paging scroll view with nested scrolling image views is too lengthy to post, which is why I suggested using the PhotoScroller app as a starting point. I have posted the additions to this sample that I would expect to enable the behaviour I am looking for.Blackman
UIScrollView has its own private gesture recognizers. Even the exposed panGestureRecognizer is a private class. NSLog(@"%@", [pagingScrollView valueForKeyPath:@"gestureRecognizers.class"]); -> ( UIScrollViewDelayedTouchesBeganGestureRecognizer, UIScrollViewPanGestureRecognizer, UIScrollViewPagingSwipeGestureRecognizer ). I think you'll need to pick a different UI.Amandine
I think you will need to subclass it, as stated in the documentation "Subclasses can override the touchesShouldBegin:withEvent:inContentView:, pagingEnabled, and touchesShouldCancelInContentView: methods (which are called by the scroll view) to affect how the scroll view handles scrolling gestures."Factotum
B
1

It would appear that this is either impossible, or very very difficult (i.e. roll your own scroll view from scratch). I used an Apple Tech support incident and they were unable to help. If a solution becomes available I'll happily mark it as the accepted answer!

Blackman answered 11/1, 2013 at 15:50 Comment(0)
R
1

PROBLEM:

When the UIPanGestureRecognizer is underlying a UIScrollView (which unfortunately does also effect UIPageViewController) the maximumNumberOfTouches is not behaving as expected - the minimumNumberOfTouches however always limits the lower end correctly.

When monitoring these parameters they seem to do their job - it's just that UIScrollView doesn't honor them and ignores their values!


REMEDY:

You can find the solution in my answer to:

UIScrollView scrolling only with one finger


BTW:

The difference you experience between one finger and two finger panning is because with one finger you are using the panGestureRecognizer. With two fingers the pinchGestureRecognizer (which can also pan at the same time) kicks in and you have no deceleration phase and the view stops panning and zooming immediately after releasing your fingers. Deactivate the pinchGestureRecognizer and you will see that the panGestureRecognizer takes over - even for two fingers - and panning is smooth again... ;-)


SIMULTANEOUSLY - et voilà...

Delegate callbacks for a perfect simultaneous 2 finger scrolling and zooming behavior:

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
    self.pinchGestureRecognizer.enabled = NO;
}

- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView {
    self.pinchGestureRecognizer.enabled = NO;
}

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
    self.pinchGestureRecognizer.enabled = YES;
}

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
    self.pinchGestureRecognizer.enabled = YES;
}

- (void)scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view {    
    self.panGestureRecognizer.enabled = NO;
}

- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(CGFloat)scale {    
    self.panGestureRecognizer.enabled = YES;
}

Fast pinch starts zooming!
Fast pan starts panning!

Stopping a decelerating pan with two new fingers down on the screen and start dragging again does not let the clumsy pinchGestureRecognizer take over (default) but rather smoothly go into the next pan/deceleration phase - like with one finger!


FOR PERFECTIONISTS:

Put 2 fingers on the screen and - DON'T MOVE NOW - If you don't start pinching within the first 0.5 seconds zooming gets locked and only panning is available:

- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {

    if ([gestureRecognizer.view isMemberOfClass:[MY_CustomScrollView class]]) {

        NSLog(@"IN SCROLL-VIEW...");
        if (gestureRecognizer == self.pinchGestureRecognizer) {
            if (_pinchGestureLocked) {
                NSLog(@"...but TOO late for zooming...");
                return NO;
            } else {
                NSLog(@"...ZOOMING + PANNING...");
                return YES;
            }
        } else if (gestureRecognizer == self.panGestureRecognizer){
            if (gestureRecognizer.numberOfTouches > 2) {
                NSLog(@"...but TOO many touches for PANNING ONLY...");
                return NO;
            } else {
                NSLog(@"...PANNING...");
                return YES;
            }
        } else {

            NSLog(@"...NO PAN or PINCH...");
            return YES;
        }

    } else {
        NSLog(@"NOT IN SCROLL-VIEW...");
    }

    return YES;

}  

BOOL _pinchGestureLocked = NO;

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [super touchesBegan:touches withEvent:event];
    _pinchGestureLocked = YES;
}

- (void) touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [super touchesCancelled:touches withEvent:event];
    _pinchGestureLocked = NO;
}

- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [super touchesEnded:touches withEvent:event];
    _pinchGestureLocked = NO;
}

Happy gesture-recognizing!


Rademacher answered 16/10, 2016 at 17:9 Comment(0)
F
0

Subclassing seems to be the way to go with this

The documentation states that you can manipulate how scrolling gestures are handled through subclassing.

UIScrollView Class Reference

Specifically

Subclasses can override the touchesShouldBegin:withEvent:inContentView:, pagingEnabled, and touchesShouldCancelInContentView: methods (which are called by the scroll view) to affect how the scroll view handles scrolling gestures.

You should also take a look at this SO post, Scrolling with two fingers with a UIScrollView

Factotum answered 15/11, 2011 at 18:38 Comment(4)
Thanks for the response, I appreciate it. However - being critical for a minute - this is a very generic answer to a more specific question. The ImageScrollView is already subclassed, and I have experimented with subclassing the paging scroll view as well, to no avail. I have seen the linked SO post before, and while it has been useful to me in the past, the answers are actually all outdated (even the ones that iterate through the scroll view's gesture recognisers - the same can be more easily achieved in iOS 5 using the code in my question).Blackman
I hope you can appreciate that I would like to issue the bounty to an answer that provides a little more detailed insight into how one might customise a subclass to achieve the required behaviour. For example, since touchesShouldBegin... & touchesShouldCancel... are only called once at the start of each touch sequence, how do I handle the case where the ImageScrollView should pan (while zoomed in), and then the paging scroll view should take over when scrolling reaches the edge of the image?Blackman
I can fully appreciate that, I hope that you can get a more helpful answer. I will see if I can look into it some. I have used gesture recognizers with scrollviews but not this scenario.Factotum
Thanks, I'd appreciate the help if you find any further information.Blackman

© 2022 - 2024 — McMap. All rights reserved.