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