Uipinchgesturerecognizer. Make Zoom in Location of Fingers, Not Only Center

UIPinchGestureRecognizer position the pinched view between the two fingers

You can get the CGPoint of the midpoint between two fingers via the following code in the method handlingPinchGesture.

CGPoint point = [sender locationInView:self];

My whole handlePinchGesture method is below.

/*
instance variables

CGFloat lastScale;
CGPoint lastPoint;
*/

- (void)handlePinchGesture:(UIPinchGestureRecognizer *)sender {
if ([sender numberOfTouches] < 2)
return;

if (sender.state == UIGestureRecognizerStateBegan) {
lastScale = 1.0;
lastPoint = [sender locationInView:self];
}

// Scale
CGFloat scale = 1.0 - (lastScale - sender.scale);
[self.layer setAffineTransform:
CGAffineTransformScale([self.layer affineTransform],
scale,
scale)];
lastScale = sender.scale;

// Translate
CGPoint point = [sender locationInView:self];
[self.layer setAffineTransform:
CGAffineTransformTranslate([self.layer affineTransform],
point.x - lastPoint.x,
point.y - lastPoint.y)];
lastPoint = [sender locationInView:self];
}

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

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;
}

How to start a Pinch Zoom Gesture from previous scale?

The variable lastScale will always be 1 because this method is deleted out of memory once it is used, until it is called again. Therefore, lastScale will always reset to 1. On top of that, you have recognizer.state == began and setting lastScale = 1 which means that everytime a new touch is called, lastscale = 1.

What you should be doing is creating a global variable, not local variable, and adjusting that scale. This will allow it to not reset back to 1 everytime. Also, don’t ever reset lastScale unless you press some reset function. Think about it - why would you want to reset your lastScale after it is set?

iOS Pinch Zoom Start from Previous Scale

I think you might instead want to initialize lastScale to 1.0 when your gesture begins.

Look at the accepted answer to this question.

Is there a gesture recognizer that handles both pinch and pan together?

So I created the custom gesture recognizer in light of no one giving me a better solution that achieved the desired results. Below are the key code fragments that allow the custom recognizer to indicate where the view should reposition and what its new scale should be with the centroid as the center of the pan and zoom effects so that the pixels under the fingers remain under the fingers at all time, unless the fingers appear to rotate, which is not supported and I can't do anything to stop them from such a gesture. This gesture recognizer pans and zooms simultaneously with two fingers. I need to add support later for one finger panning, even when one of two fingers is lifted up.

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
// We can only process if we have two fingers down...
if ( FirstFinger == nil || SecondFinger == nil )
return;

// We do not attempt to determine if the first finger, second finger, or
// both fingers are the reason for this method call. For this reason, we
// do not know if either is stale or updated, and thus we cannot rely
// upon the UITouch's previousLocationInView method. Therefore, we need to
// cache the latest UITouch's locationInView information each pass.

// Break down the previous finger coordinates...
float A0x = PreviousFirstFinger.x;
float A0y = PreviousFirstFinger.y;
float A1x = PreviousSecondFinger.x;
float A1y = PreviousSecondFinger.y;
// Update our cache with the current fingers for next pass through here...
PreviousFirstFinger = [FirstFinger locationInView:nil];
PreviousSecondFinger = [SecondFinger locationInView:nil];
// Break down the current finger coordinates...
float B0x = PreviousFirstFinger.x;
float B0y = PreviousFirstFinger.y;
float B1x = PreviousSecondFinger.x;
float B1y = PreviousSecondFinger.y;

// Calculate the zoom resulting from the two fingers moving toward or away from each other...
float OldScale = Scale;
Scale *= sqrt((B0x-B1x)*(B0x-B1x) + (B0y-B1y)*(B0y-B1y))/sqrt((A0x-A1x)*(A0x-A1x) + (A0y-A1y)*(A0y-A1y));

// Calculate the old and new centroids so that we can compare the centroid's movement...
CGPoint OldCentroid = { (A0x + A1x)/2, (A0y + A1y)/2 };
CGPoint NewCentroid = { (B0x + B1x)/2, (B0y + B1y)/2 };

// Calculate the pan values to apply to the view so that the combination of zoom and pan
// appear to apply to the centroid rather than the center of the view...
Center.x = NewCentroid.x + (Scale/OldScale)*(self.view.center.x - OldCentroid.x);
Center.y = NewCentroid.y + (Scale/OldScale)*(self.view.center.y - OldCentroid.y);
}

The view controller handles the events by assigning the new scale and center to the view in question. I noticed that other gesture recognizers tend to allow the controller to do some of the math, but I tried to do all the math in the recognizer.

-(void)handlePixelTrack:(PixelTrackGestureRecognizer*)sender
{
sender.view.center= sender.Center;
sender.view.transform = CGAffineTransformMakeScale(sender.Scale, sender.Scale);
}


Related Topics



Leave a reply



Submit