How would I be able to limit the scale of the UIPinchGestureRecognizer to a min and max level? The scale property below seems to be relative to the last known scale (the de
Thanks, really useful code snippet above clamping to a minimum and maximum scale.
I found that when I flipped the view first using:
CGAffineTransformScale(gestureRecognizer.view.transform, -1.0, 1.0);
it would cause a flicker when scaling the view.
Let me know what you think but the solution for me was to update the code sample above, and if the view has been flipped (flag set via property) then invert the scale value:
if ([gestureRecognizer state] == UIGestureRecognizerStateBegan || [gestureRecognizer state] == UIGestureRecognizerStateChanged)
{
CGFloat currentScale = [[[gestureRecognizer view].layer valueForKeyPath:@"transform.scale"] floatValue];
if(self.isFlipped) // (inverting)
{
currentScale *= -1;
}
CGFloat newScale = 1 - (self.lastScale - [gestureRecognizer scale]);
newScale = MIN(newScale, self.maximumScaleFactor / currentScale);
newScale = MAX(newScale, self.minimumScaleFactor / currentScale);
CGAffineTransform transform = CGAffineTransformScale([[gestureRecognizer view] transform], newScale, newScale);
gestureRecognizer.view.transform = transform;
self.lastScale = [gestureRecognizer scale]; // Store the previous scale factor for the next pinch gesture call
Can you use a scroll view instead? Then you could use scrollView.minimumZoomScale and scrollView.maximumZoomScale
The problem with most of the other answers is that they are trying to deal with scale as a linear value, when in fact it is non-linear due to the way UIPinchGestureRecognizer
calculates its scale property based on the touch distance. When this isn't taken into account, the user must use more or less pinch distance to 'undo' the scaling applied by a previous pinch gesture.
Consider: suppose transform.scale
= 1.0
and I place my fingers 6cm apart on the screen, then pinch inwards to 3cm apart - the resulting gestureRecognizer.scale
is 0.5
, and 0.5-1.0
is -0.5
, so transform.scale
will become 1.0+(-0.5)
= 0.5
. Now, I lift my fingers, place them back down 3cm apart and pinch outwards to 6cm. The resulting gestureRecognizer.scale
will be 2.0
, and 2.0-1.0
is 1.0
, so transform.scale
will become 0.5+1.0
= 1.5
. Not what I wanted to happen.
The fix is to calculate the delta pinch scale as a proportion of its previous value. I place my fingers down 6cm apart, and pinch inwards to 3cm, so gestureRecognizer.scale
is 0.5
. 0.5/1.0
is 0.5
, so my new transform.scale
is 1.0*0.5
= 0.5
. Next, I place my fingers down 3cm apart, and pinch outwards to 6cm. gestureRecognizer.scale
is then 2.0
, and 2.0/1.0
is 2.0
, so my new transform.scale
is 0.5*2.0
= 1.0
, which is exactly what I wanted to happen.
Here it is in code:
in -(void)viewDidLoad
:
self.zoomGestureCurrentZoom = 1.0f;
in -(void)onZoomGesture:(UIPinchGestureRecognizer*)gestureRecognizer
:
if ( gestureRecognizer.state == UIGestureRecognizerStateBegan )
{
self.zoomGestureLastScale = gestureRecognizer.scale;
}
else if ( gestureRecognizer.state == UIGestureRecognizerStateChanged )
{
// we have to jump through some hoops to clamp the scale in a way that makes the UX intuitive
float scaleDeltaFactor = gestureRecognizer.scale/self.zoomGestureLastScale;
float currentZoom = self.zoomGestureCurrentZoom;
float newZoom = currentZoom * scaleDeltaFactor;
// clamp
float kMaxZoom = 4.0f;
float kMinZoom = 0.5f;
newZoom = MAX(kMinZoom,MIN(newZoom,kMaxZoom));
self.view.transform = CGAffineTransformScale([[gestureRecognizer view] transform], newZoom, newZoom);
// store for next time
self.zoomGestureCurrentZoom = newZoom;
self.zoomGestureLastScale = gestureRecognizer.scale;
}
Other approaches mentioned here did not work for me, but taking a couple things from previous answers and (in my opinion) simplifying things, I've got this to work for me. effectiveScale
is an ivar set to 1.0 in viewDidLoad
.
-(void)zoomScale:(UIPinchGestureRecognizer *)recognizer
{
if([recognizer state] == UIGestureRecognizerStateEnded) {
// Reset last scale
lastScale = 1.0;
return;
}
if ([recognizer state] == UIGestureRecognizerStateBegan ||
[recognizer state] == UIGestureRecognizerStateChanged) {
CGFloat pinchscale = [recognizer scale];
CGFloat scaleDiff = pinchscale - lastScale;
if (scaleDiff < 0)
scaleDiff *= 2; // speed up zoom-out
else
scaleDiff *= 0.7; // slow down zoom-in
effectiveScale += scaleDiff;
// Limit scale between 1 and 2
effectiveScale = effectiveScale < 1 ? 1 : effectiveScale;
effectiveScale = effectiveScale > 2 ? 2 : effectiveScale;
// Handle transform in separate method using new effectiveScale
[self makeAndApplyAffineTransform];
lastScale = pinchscale;
}
}