Using PinchGesture; how can I zoom in to where a user's fingers actually "pinch"?

Brittany picture Brittany · Sep 16, 2014 · Viewed 13.6k times · Source

I've implemented the UIPinchGestureRecognizer on a UIImageView in my app, however no matter where I pinch on the image, it seems to zoom into the same spot. Does anyone know how I can make it zoom in to where a user actually "pinches"? See code below.

ViewController.m

 - (IBAction)scaleImage:(UIPinchGestureRecognizer *)recognizer {

   recognizer.view.transform = CGAffineTransformScale(recognizer.view.transform, recognizer.scale, recognizer.scale);
   recognizer.scale = 1; 

 }

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

    if (gestureRecognizer == tap) {
        shouldReceiveTouch = (touch.view == featureImage);
    }
    return shouldReceiveTouch;
}

Answer

rob mayoff picture rob mayoff · Sep 16, 2014

A scale transform leaves the origin (0, 0) untouched. So to scale a view around a particular point, you must first translate that point to the origin, then apply the scale, then translate back.

- (IBAction)pinchGestureDidFire:(UIPinchGestureRecognizer *)pinch {

First, we get the view being pinched.

    UIView *pinchView = pinch.view;

To compute the center of the pinch, we'll need the midpoint of the view's bounds, so we get the bounds too:

    CGRect bounds = pinchView.bounds;

The center is based on the centroid of the pinch's touches, which we get this way:

    CGPoint pinchCenter = [pinch locationInView:pinchView];

But we actually need the pinch offset relative to the center of the view, because the view's transform is relative to the center of the view by default. (You can change this by changing the view's layer.anchorPoint.)

    pinchCenter.x -= CGRectGetMidX(bounds);
    pinchCenter.y -= CGRectGetMidY(bounds);

Now we can update the view's transform. First we get its current transform:

    CGAffineTransform transform = pinchView.transform;

Then we update it to translate the pinch center to the origin:

    transform = CGAffineTransformTranslate(transform, pinchCenter.x, pinchCenter.y);

Now we can apply the scale:

    CGFloat scale = pinch.scale;
    transform = CGAffineTransformScale(transform, scale, scale);

Then we translate the view back:

    transform = CGAffineTransformTranslate(transform, -pinchCenter.x, -pinchCenter.y);

Now we can update the view with the modified transform:

    pinchView.transform = transform;

Finally, we reset the gesture recognizer's scale, since we've applied the current scale:

    pinch.scale = 1.0;
}

Demo:

pinch scale

Note that in the simulator, you can hold option (alt) for a pinch gesture. Holding shift (while holding option) moves the two touches together.

Here's the code all together for copy/paste:

- (IBAction)pinchGestureDidFire:(UIPinchGestureRecognizer *)pinch {
    UIView *pinchView = pinch.view;
    CGRect bounds = pinchView.bounds;
    CGPoint pinchCenter = [pinch locationInView:pinchView];
    pinchCenter.x -= CGRectGetMidX(bounds);
    pinchCenter.y -= CGRectGetMidY(bounds);
    CGAffineTransform transform = pinchView.transform;
    transform = CGAffineTransformTranslate(transform, pinchCenter.x, pinchCenter.y);
    CGFloat scale = pinch.scale;
    transform = CGAffineTransformScale(transform, scale, scale);
    transform = CGAffineTransformTranslate(transform, -pinchCenter.x, -pinchCenter.y);
    pinchView.transform = transform;
    pinch.scale = 1.0;
}