Max/Min Scale of Pinch Zoom in UIPinchGestureRecognizer - iPhone iOS Max/Min Scale of Pinch Zoom in UIPinchGestureRecognizer - iPhone iOS ios ios

Max/Min Scale of Pinch Zoom in UIPinchGestureRecognizer - iPhone iOS


Here is the solution that I figured out after using Anomie's answer as a starting point.

- (void)handlePinchGesture:(UIPinchGestureRecognizer *)gestureRecognizer {    if([gestureRecognizer state] == UIGestureRecognizerStateBegan) {        // Reset the last scale, necessary if there are multiple objects with different scales        lastScale = [gestureRecognizer scale];    }    if ([gestureRecognizer state] == UIGestureRecognizerStateBegan ||         [gestureRecognizer state] == UIGestureRecognizerStateChanged) {        CGFloat currentScale = [[[gestureRecognizer view].layer valueForKeyPath:@"transform.scale"] floatValue];        // Constants to adjust the max/min values of zoom        const CGFloat kMaxScale = 2.0;        const CGFloat kMinScale = 1.0;        CGFloat newScale = 1 -  (lastScale - [gestureRecognizer scale]);         newScale = MIN(newScale, kMaxScale / currentScale);           newScale = MAX(newScale, kMinScale / currentScale);        CGAffineTransform transform = CGAffineTransformScale([[gestureRecognizer view] transform], newScale, newScale);        [gestureRecognizer view].transform = transform;        lastScale = [gestureRecognizer scale];  // Store the previous scale factor for the next pinch gesture call      }}


There isn't a way to limit the scale on a UIPinchGestureRecognizer. To limit the height in your code, you should be able to do something like this:

CGFloat scale = 1.0 - (lastScale - pinchscale);CGRect bounds = [(UIPinchGestureRecognizer*)sender view].bounds;scale = MIN(scale, maximumHeight / CGRectGetHeight(bounds));scale = MAX(scale, minimumHeight / CGRectGetHeight(bounds));

To limit width, change 'Height' to 'Width' in the last two lines.


I took some info gleaned from Paul Solt and Anoime's answers, and added that to an existing category I have made for UIViewController to allow making any UIView draggable, to now make it pinchable using gestures and transforms.

Note: this dirties the tag property of the view you are making draggable/pinchable. So if you needed the tag for something else, you can consider placing that value in the NSMutableDictionary being used by this technique. That's available as [self dictForView:theView]

Implementing in your project:

You can make any subview within the view controllers "view" draggable or pinchable (or both)place a single line of code in your viewDidLoad (for example:)

[self makeView:mySubView draggable:YES pinchable:YES minPinchScale:0.75 maxPinchScale:1.0];

turn it off in viewDidUnload (releases guestures & dictionary):

[self makeView:mySubView draggable:NO pinchable:NO minPinchScale:1.0 maxPinchScale:1.0];

DragAndPinchScale.h file

#import <UIKit/UIKit.h>@interface UIViewController (DragAndPinchScale)-(void) makeView:(UIView*)aView        draggable:(BOOL)draggable        pinchable:(BOOL)pinchable    minPinchScale:(CGFloat)minPinchScale   maxPinchScale:(CGFloat)maxPinchScale;-(NSMutableDictionary *) dictForView:(UIView *)theView;-(NSMutableDictionary *) dictForViewGuestures:(UIGestureRecognizer *)guesture;@end

DragAndPinchScale.m file

#import "DragAndPinchScale.h"@implementation UIViewController (DragAndPinchScale)-(NSMutableDictionary *) dictForView:(UIView *)theView{    NSMutableDictionary *dict = (NSMutableDictionary*) (void*) theView.tag;    if (!dict) {        dict = [[NSMutableDictionary dictionary ] retain];        theView.tag = (NSInteger) (void *) dict;    }    return dict;}-(NSMutableDictionary *) dictForViewGuestures:(UIGestureRecognizer *)guesture {    return [self dictForView:guesture.view];}- (IBAction)fingersDidPinchInPinchableView:(UIPinchGestureRecognizer *)fingers {    NSMutableDictionary *dict = [self dictForViewGuestures:fingers];    UIView *viewToZoom = fingers.view;    CGFloat lastScale;    if([fingers state] == UIGestureRecognizerStateBegan) {        // Reset the last scale, necessary if there are multiple objects with different scales        lastScale = [fingers scale];    } else {        lastScale = [[dict objectForKey:@"lastScale"] floatValue];    }    if ([fingers state] == UIGestureRecognizerStateBegan ||         [fingers state] == UIGestureRecognizerStateChanged) {        CGFloat currentScale = [[[fingers view].layer valueForKeyPath:@"transform.scale"] floatValue];        // limits to adjust the max/min values of zoom        CGFloat maxScale = [[dict objectForKey:@"maxScale"] floatValue];        CGFloat minScale = [[dict objectForKey:@"minScale"] floatValue];        CGFloat newScale = 1 -  (lastScale - [fingers scale]);         newScale = MIN(newScale, maxScale / currentScale);           newScale = MAX(newScale, minScale / currentScale);        CGAffineTransform transform = CGAffineTransformScale([[fingers view] transform], newScale, newScale);        viewToZoom.transform = transform;        lastScale = [fingers scale];  // Store the previous scale factor for the next pinch gesture call      }    [dict setObject:[NSNumber numberWithFloat:lastScale]              forKey:@"lastScale"];}- (void)fingerDidMoveInDraggableView:(UIPanGestureRecognizer *)finger {    NSMutableDictionary *dict = [self dictForViewGuestures:finger];    UIView *viewToDrag =  finger.view;    if (finger.state == UIGestureRecognizerStateBegan) {        [dict setObject:[NSValue valueWithCGPoint:viewToDrag.frame.origin]                  forKey:@"startDragOffset"];        [dict setObject:[NSValue valueWithCGPoint:[finger locationInView:self.view]]                  forKey:@"startDragLocation"];    }    else if (finger.state == UIGestureRecognizerStateChanged) {        NSMutableDictionary *dict = (NSMutableDictionary*) (void*) viewToDrag.tag;        CGPoint stopLocation = [finger locationInView:self.view];        CGPoint startDragLocation = [[dict valueForKey:@"startDragLocation"] CGPointValue];        CGPoint startDragOffset = [[dict valueForKey:@"startDragOffset"] CGPointValue];        CGFloat dx = stopLocation.x - startDragLocation.x;        CGFloat dy = stopLocation.y - startDragLocation.y;        //   CGFloat distance = sqrt(dx*dx + dy*dy );        CGRect dragFrame = viewToDrag.frame;        CGSize selfViewSize = self.view.frame.size;        if (!UIDeviceOrientationIsPortrait(self.interfaceOrientation)) {            selfViewSize = CGSizeMake(selfViewSize.height,selfViewSize.width);        }        selfViewSize.width  -= dragFrame.size.width;        selfViewSize.height -= dragFrame.size.height;        dragFrame.origin.x = MIN(selfViewSize.width, MAX(0,startDragOffset.x+dx));        dragFrame.origin.y = MIN(selfViewSize.height,MAX(0,startDragOffset.y+dy));        viewToDrag.frame = dragFrame;    }    else if (finger.state == UIGestureRecognizerStateEnded) {        [dict removeObjectForKey:@"startDragLocation"];        [dict removeObjectForKey:@"startDragOffset"];    }}-(void) makeView:(UIView*)aView        draggable:(BOOL)draggable        pinchable:(BOOL)pinchable    minPinchScale:(CGFloat)minPinchScale   maxPinchScale:(CGFloat)maxPinchScale{    NSMutableDictionary *dict = (NSMutableDictionary*) (void*) aView.tag;    if (!(pinchable || draggable)) {        if (dict){             [dict release];            aView.tag = 0;        }        return;    }    if (dict) {        UIPanGestureRecognizer *pan =[dict objectForKey:@"UIPanGestureRecognizer"];        if(pan){            if ([aView.gestureRecognizers indexOfObject:pan]!=NSNotFound) {                [aView removeGestureRecognizer:pan];            }            [dict removeObjectForKey:@"UIPanGestureRecognizer"];        }        UIPinchGestureRecognizer *pinch =[dict objectForKey:@"UIPinchGestureRecognizer"];        if(pinch){            if ([aView.gestureRecognizers indexOfObject:pinch]!=NSNotFound) {                [aView removeGestureRecognizer:pinch];            }            [dict removeObjectForKey:@"UIPinchGestureRecognizer"];        }        [dict removeObjectForKey:@"startDragLocation"];        [dict removeObjectForKey:@"startDragOffset"];        [dict removeObjectForKey:@"lastScale"];        [dict removeObjectForKey:@"minScale"];        [dict removeObjectForKey:@"maxScale"];    }    if (draggable) {        UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(fingerDidMoveInDraggableView:)];        pan.minimumNumberOfTouches = 1;          pan.maximumNumberOfTouches = 1;          [aView addGestureRecognizer:pan];        [pan release];        dict = [self dictForViewGuestures:pan];        [dict setObject:pan forKey:@"UIPanGestureRecognizer"];    }    if (pinchable) {        CGAffineTransform initialTramsform = CGAffineTransformMakeScale(1.0, 1.0);        aView.transform = initialTramsform;        UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(fingersDidPinchInPinchableView:)];        [aView addGestureRecognizer:pinch];        [pinch release];        dict = [self dictForViewGuestures:pinch];        [dict setObject:pinch forKey:@"UIPinchGestureRecognizer"];        [dict setObject:[NSNumber numberWithFloat:minPinchScale] forKey:@"minScale"];        [dict setObject:[NSNumber numberWithFloat:maxPinchScale] forKey:@"maxScale"];    }}@end