Doubletap and slide/drag finger to zoom in/out

2019-05-10 10:32发布

问题:

If anyone came across the google maps app for iOS there is a great feature to zoom in/out with one finger: Doubletap on the uiscrollview and then immediately slide finger up or down to zoom in/out. Does anyone know how this is achieved? Did google post any snippet of that?

回答1:

I added this functionality to my UIScrollView category.
The actual tap recognition is easy, calculating the "correct" (whatever feels "correct") zoomScale is the problem… If you think the category isn't handling this good enough, please don't hesitate to tell me and open a new issue on the github page.



回答2:

This sample uses regular zooming capabilities of UIScrollView, which contains UIImageView as subview. You can find the implementation of such zooming in MWPhotoBrowser library for example. _imageView, _doubleTapBeganPoint, _longPressBeganPoint, _minScale are your class (UIScrollView subclass) iVars. So begin with initialization:

UILongPressGestureRecognizer* lpgs = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(imageLongPressed:)];
lpgs.minimumPressDuration = .2;
[self addGestureRecognizer:lpgs];

Standard zoom handler:

- (UIView*)viewForZoomingInScrollView:(UIScrollView *)scrollView {
    return _imageView;
}

Using touchesBegan to catch double tap (UITapGestureRecognizer doesn't want to work with UILongPressGestureRecognizer in some reason):

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

    UITouch *touch = [touches anyObject];
    NSUInteger tapCount = touch.tapCount;
    switch (tapCount) {
        case 2:
            [self handleDoubleTapBegan:[touch locationInView:self.superview]];
            break;
        default:
            break;
    }
    [[self nextResponder] touchesEnded:touches withEvent:event];
}

- (void)handleDoubleTapBegan:(CGPoint)touchPoint {

    _doubleTapBeganPoint = touchPoint;

    NSLog(@"image double tap began at location: %@", NSStringFromCGPoint(touchPoint));
}

Handle long press and using difference in Y coordinates to calculate zoom scale. _minScale stores your initial zoomScale so we can restore it.

- (void) imageLongPressed:(UIGestureRecognizer*)gesture {

    if (gesture.state == UIGestureRecognizerStateBegan)
    {
        self.maximumZoomScale = _maxScale * 2;
        self.minimumZoomScale = _minScale / 3;

        _longPressBeganPoint = [gesture locationInView:self.superview];

        [self setZoomScale:_minScale animated:YES];
        NSLog(@"image long press began at location: %@", NSStringFromCGPoint(_longPressBeganPoint));
    }
    else if (gesture.state == UIGestureRecognizerStateChanged)
    {
        CGPoint p = [gesture locationInView:self.superview];

        //NSLog(@"image long press changed at location: %@", NSStringFromCGPoint(p));

        if (CGPointEqualToPoint(_longPressBeganPoint, _doubleTapBeganPoint))
        {
            _zoom = _minScale + (p.y - _longPressBeganPoint.y) / 100.0;

            NSLog(@"zoom scale: %f", _zoom);

            [self setZoomScale:_zoom animated:NO];
        }
    }
    else if (gesture.state == UIGestureRecognizerStateEnded)
    {
        NSLog(@"image long press ended at location: %@", NSStringFromCGPoint([gesture locationInView:gesture.view]));

        if (self.zoomScale < _minScale)
        {
            [self setZoomScale:_minScale animated:YES];
            NSLog(@"min zoom scale: %f", _minScale);
        }
    }
}


回答3:

Use a UIPanGestureRecognizer to track the up/down drag. To ensure it only gets triggered on a double-tap, give it a delegate that does the following:

func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
    if gestureRecognizer == yourZoomRecognizer {
        return touch.tapCount == 2
    } else {
        return true
    }
}

Now handle the messages coming from the gesture-recogniser. You can derive a zoom scale like so:

let zoomFactor: CGFloat = 1.01        //Each point of pan zooms in or out by this much
var zoomScale: CGFloat = 1            //Dummy variable for example purposes.

@IBAction func handleZoomGesture(sender: UIPanGestureRecognizer) {
    if sender.state == .began {
        //Set initial translation to reflect the current zoomScale
        let logZoom = log(zoomScale) / log(zoomFactor)
        sender.setTranslation(CGPoint(x: 0, y: logZoom), in: sender.view)
    } else if sender.state == .changed {
        let logZoom = sender.translation(in: sender.view).y
        zoomScale = pow(zoomFactor, logZoom)
    }
}

This doesn't show actually setting the zoomScale on a scrollview, which is a bunch of extra code - you'll want to get the pan gesture's location and center the zoom around that. You'll also want to clamp the zoomScale, and maybe handle the zoom ending too.