I am doing the project in which I have to show one Image, when I click on the image one "white view "will come on the upper side of the image. And when I double click on the "two pins" will come out on the "white view". When I pull these two pins the width and height of the white view increase and decrease.
I am not able to do this.
Any Idea from experts would be highly welcome.
Second_View.h file
#import <UIKit/UIKit.h>
#interface Second_View : UIViewController<UIGestureRecognizerDelegate>{
UITapGestureRecognizer *tap;
UISwipeGestureRecognizer *swipe;
UIPanGestureRecognizer *pan;
CGPoint startLocation;
CGFloat lastScale;
CGFloat firstX;
CGFloat firstY;
CGPoint lastLocation;
UIImageView *imageVw;
}
#property (nonatomic) CGPoint center;
#property (nonatomic) NSInteger count;
#property(nonatomic,retain) UIImageView *imageVw;
#end
Second_View.m file
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = [UIColor grayColor];
imageVw = [[UIImageView alloc]initWithFrame:CGRectMake(200, 200, 500, 400)];
imageVw.image = [UIImage imageNamed:#"redacted2.jpg"];
imageVw.userInteractionEnabled=YES;
imageVw.autoresizesSubviews = YES;
imageVw.alpha = 0.93; // for opacity
[self.view addSubview:imageVw];
tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleTap:)];
tap.numberOfTapsRequired = 1;
[imageVw addGestureRecognizer:tap];
tap.delegate=self;
pan=[[UIPanGestureRecognizer alloc]initWithTarget:self action:#selector(panAction:)];
[pan setMinimumNumberOfTouches:1];
[pan setMaximumNumberOfTouches:2];
[pan setDelegate:self];
count=0;
[imageVw addGestureRecognizer:pan];
}
- (void)panAction:(UIPanGestureRecognizer *)gestureRecognizer {
CGPoint translatedPoint = [(UIPanGestureRecognizer*)gestureRecognizer translationInView:self.view];
if ([(UIPanGestureRecognizer*)gestureRecognizer state] == UIGestureRecognizerStateBegan) {
firstX = [[gestureRecognizer view] center].x;
firstY = [[gestureRecognizer view] center].y;
}
translatedPoint = CGPointMake(firstX+translatedPoint.x, firstY+translatedPoint.y);
[[gestureRecognizer view] setCenter:translatedPoint];
if ([(UIPanGestureRecognizer*)gestureRecognizer state] == UIGestureRecognizerStateEnded) {
CGFloat velocityX = (0.2*[(UIPanGestureRecognizer*)gestureRecognizer velocityInView:self.view].x);
CGFloat finalX = translatedPoint.x + velocityX;
CGFloat finalY = firstY;// translatedPoint.y + (.35*[(UIPanGestureRecognizer*)sender velocityInView:self.view].y);
if (UIDeviceOrientationIsPortrait([[UIDevice currentDevice] orientation])) {
if (finalX < 0) {
//finalX = 0;
} else if (finalX > 768) {
//finalX = 768;
}
if (finalY < 0) {
finalY = 0;
} else if (finalY > 1024) {
finalY = 1024;
}
} else {
if (finalX < 0) {
//finalX = 0;
} else if (finalX > 1024) {
//finalX = 768;
}
if (finalY < 0) {
finalY = 0;
} else if (finalY > 768) {
finalY = 1024;
}
}
}
}
-(void)handleTap:(UIPanGestureRecognizer *)gestureRecognizer
{
CGPoint location = [gestureRecognizer locationInView:gestureRecognizer.view];
NSLog(#"x:%f y:%f",location.x,location.y);
NSArray *subViewsOfImage = [imageVw subviews];
for (id subView in subViewsOfImage) {
CGRect frame1=[subView frame];
int x=0;
int y=0;
x=frame1.origin.x+frame1.size.width;
y=frame1.origin.y+frame1.size.height;
if ((location.x>=frame1.origin.x && location.x<x) && (location.y>=frame1.origin.y && location.y<y) )
{
NSLog(#"No");
return;
}
}
derivedView *view=[[derivedView alloc]initWithFrame:CGRectMake(location.x, location.y, 100, 30)];
[view setBackgroundColor:[UIColor whiteColor]];
[imageVw addSubview: view];
NSArray * subViewsOfImage1 = [imageVw subviews];
NSLog(#"subViewsOfImage = %#",subViewsOfImage1);
NSLog(#"Yes");
}
derivedView.h file
#import <UIKit/UIKit.h>
#interface derivedView : UIView<UIGestureRecognizerDelegate>
{
UIPanGestureRecognizer *pan;
UITapGestureRecognizer *tap1;
UITapGestureRecognizer *tap2;
UITapGestureRecognizer *tap3;
CGPoint lastLocation;
CGFloat firstX;
CGFloat firstY;
UIImageView *rPinImgView;
UIImageView *lPinImgView;
}
#end
derivedView.m file
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
pan=[[UIPanGestureRecognizer alloc]initWithTarget:self action:#selector(panAction:)];
[pan setDelegate:self];
[self addGestureRecognizer:pan];
tap1=[[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(tapAction:)];
[tap1 setDelegate:self];
[self addGestureRecognizer:tap1];
}
return self;
}
- (void)tapAction:(UITapGestureRecognizer *)gestureRecognizer {
// lPinImgView=[[UIImageView alloc]initWithFrame:CGRectMake(-15,-20, 30, 30)];
// lPinImgView.image=[UIImage imageNamed:#"pin"];
// // pin.frame=CGRectMake(frame1.origin.x, frame1.origin.y-15, 10, 10);
//
// rPinImgView=[[UIImageView alloc]initWithFrame:CGRectMake (self.frame.size.width-15 ,23, 30, 30)];
// //pin1.frame=CGRectMake(frame1.origin.x+frame1.size.width, frame1.origin.y+5, 10, 10);
// rPinImgView.image=[UIImage imageNamed:#"pin1"];
// [self addSubview:lPinImgView];
// [self addSubview:rPinImgView];
NSArray *subViews=[self subviews];
NSLog(#"subViews%#",subViews);
if ([subViews count]>0) {
[lPinImgView removeFromSuperview];
[rPinImgView removeFromSuperview];
}else
{
lPinImgView=[[UIImageView alloc]initWithFrame:CGRectMake(-15,-20, 30, 30)];
lPinImgView.image=[UIImage imageNamed:#"pin"];
// pin.frame=CGRectMake(frame1.origin.x, frame1.origin.y-15, 10, 10);
rPinImgView=[[UIImageView alloc]initWithFrame:CGRectMake(self.frame.size.width-15 ,23, 30, 30)];
//pin1.frame=CGRectMake(frame1.origin.x+frame1.size.width, frame1.origin.y+5, 10, 10);
rPinImgView.image=[UIImage imageNamed:#"pin1"];
tap2=[[UITapGestureRecognizer alloc]initWithTarget:self action:#selector (rPinImageTap:)];
[rPinImgView addGestureRecognizer:tap2];
[tap2 setDelegate:self];
tap3=[[UITapGestureRecognizer alloc]initWithTarget:self action:#selector(lPinImageTap:)];
[rPinImgView addGestureRecognizer:tap3];
[tap3 setDelegate:self];
[self addSubview:lPinImgView];
[self addSubview:rPinImgView];
}
- (void)lPinImageTap:(UIPanGestureRecognizer *)gestureRecognizer
{
}
- (void)rPinImageTap:(UIPanGestureRecognizer *)gestureRecognizer
{
}
- (void)panAction:(UIPanGestureRecognizer *)gestureRecognizer {
CGPoint translatedPoint = [(UIPanGestureRecognizer*)gestureRecognizer translationInView:self];
if ([(UIPanGestureRecognizer*)gestureRecognizer state] == UIGestureRecognizerStateBegan) {
firstX = [[gestureRecognizer view] center].x;
firstY = [[gestureRecognizer view] center].y;
}
// translatedPoint = CGPointMake(firstX+translatedPoint.x, firstY);
translatedPoint = CGPointMake(firstX+translatedPoint.x, firstY+translatedPoint.y);
[[gestureRecognizer view] setCenter:translatedPoint];
if ([(UIPanGestureRecognizer*)gestureRecognizer state] == UIGestureRecognizerStateEnded) {
CGFloat velocityX = (0.2*[(UIPanGestureRecognizer*)gestureRecognizer velocityInView:self].x);
CGFloat finalX = translatedPoint.x + velocityX;
CGFloat finalY = firstY;// translatedPoint.y + (.35*[(UIPanGestureRecognizer*)sender velocityInView:self.view].y);
if (UIDeviceOrientationIsPortrait([[UIDevice currentDevice] orientation])) {
if (finalX < 0) {
//finalX = 0;
} else if (finalX > 768) {
//finalX = 768;
}
if (finalY < 0) {
finalY = 0;
} else if (finalY > 1024) {
finalY = 1024;
}
} else {
if (finalX < 0) {
//finalX = 0;
} else if (finalX > 1024) {
//finalX = 768;
}
if (finalY < 0) {
finalY = 0;
} else if (finalY > 768) {
finalY = 1024;
}
}
}
}
}
Have a look at this below link and download the project and check:
https://github.com/spoletto/SPUserResizableView
I have a UIScrollView that has a zooming UIImageView, ie it implements:
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return self.imageView;
}
I'm trying to add a UIRotationGestureRecognizer to this imageView and I do it as follows:
_rotationGestureRecognizer = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:#selector(rotate:)];
[self.imageView addGestureRecognizer:_rotationGestureRecognizer];
-(void)rotate:(id)sender
{
UIRotationGestureRecognizer *rotationGestureRecognizer = [(UIRotationGestureRecognizer*)sender retain];
if(rotationGestureRecognizer.state == UIGestureRecognizerStateEnded)
{
self.lastRotation = 0.0;
return;
}
CGFloat rotation = 0.0 - (self.lastRotation - rotationGestureRecognizer.rotation);
rotationGestureRecognizer.view.transform = CGAffineTransformRotate(rotationGestureRecognizer.view.transform, rotation);
self.lastRotation = rotationGestureRecognizer.rotation;
[rotationGestureRecognizer release];
}
I'm just wondering, is it possible to even do this? It seems the UIScrollView is blocking the touches to my UIImageView because nothing is happening. Does Apple recommend to not do this with a zooming view in a UIScrollView?
Following code is working.Add gesture to scrollView instead of imageView.
UIRotationGestureRecognizer* _rotationGestureRecognizer = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:#selector(rotate:)];
[self.scrollView addGestureRecognizer:_rotationGestureRecognizer];
Swift 5 Version:
let rotationGestureRecognizer = UIRotationGestureRecognizer(target: self, action: #selector(self.rotate(_:)))
scrollView.addGestureRecognizer(rotationGestureRecognizer)
-(void)rotate:(UIRotationGestureRecognizer *)sender
{
[self bringSubviewToFront:[(UIRotationGestureRecognizer*)sender view]];
if([(UIRotationGestureRecognizer*)sender state] == UIGestureRecognizerStateEnded)
{
lastRotation = 0.0;
return;
}
CGFloat rotation = 0.0 - (lastRotation - [(UIRotationGestureRecognizer*)sender rotation]);
CGAffineTransform currentTransform = [(UIPinchGestureRecognizer*)sender view].transform;
CGAffineTransform newTransform = CGAffineTransformRotate(currentTransform, rotation);
[[(UIRotationGestureRecognizer*)sender view] setTransform:newTransform];
lastRotation = [(UIRotationGestureRecognizer*)sender rotation];
lastRotation = [(UIRotationGestureRecognizer*)sender rotation];
}
I have a problem in scaling the uiimageview which is placed inside the uiscrollview. I have googled and checked all the questions related to my problem in StackOverflow as well. I tried all the answers that are posted in the StackOverflow also. Nothing worked for me.
First I am placing the uiimageview inside uiscrollview in nib file and I am taking the image from Camera roll and filling the image view. Then I am using uirotationgesturerecognizer to rotate the image.
Here is the code that I am trying to do.
- (void)viewDidLoad
{
[super viewDidLoad];
NSLog(#"%#",[[UIDevice currentDevice] model]);
// Do any additional setup after loading the view, typically from a nib.
self.imagePicker = [[[UIImagePickerController alloc] init] autorelease];
self.picChosenImageView.layer.shouldRasterize = YES;
self.picChosenImageView.layer.rasterizationScale = [UIScreen mainScreen].scale;
self.picChosenImageView.layer.contents = (id)[UIImage imageNamed:#"test"].CGImage;
self.picChosenImageView.layer.shadowColor = [UIColor blackColor].CGColor;
self.picChosenImageView.layer.shadowOpacity = 0.8f;
self.picChosenImageView.layer.shadowRadius = 8;
self.picChosenImageView.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.picChosenImageView.bounds].CGPath;
UIRotationGestureRecognizer *rotationRecognizer = [[[UIRotationGestureRecognizer alloc]initWithTarget:self
action:#selector(handleRotate:)] autorelease];
rotationRecognizer.delegate = self;
[self.picChosenImageView addGestureRecognizer:rotationRecognizer];
self.containerView.delegate = self;
self.containerView.contentSize = self.picChosenImageView.layer.frame.size;
self.containerView.maximumZoomScale = 4.0f;
self.containerView.minimumZoomScale = 1.0f;
angle = 0.0f;
useRotation = 0.0;
isRotationStarted=FALSE;
isZoomingStarted = FALSE;
}
-(void)lockZoom
{
maximumZoomScale = self.containerView.maximumZoomScale;
minimumZoomScale = self.containerView.minimumZoomScale;
self.containerView.maximumZoomScale = 1.0;
self.containerView.minimumZoomScale = 1.0;
self.containerView.clipsToBounds = false;
self.containerView.scrollEnabled = false;
}
-(void)unlockZoom
{
self.containerView.maximumZoomScale = maximumZoomScale;
self.containerView.minimumZoomScale = minimumZoomScale;
self.containerView.clipsToBounds = true;
self.containerView.scrollEnabled = true;
}
#pragma mark - ScrollView delegate methods
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return self.picChosenImageView;
}
- (void)scrollViewDidZoom:(UIScrollView *)scrollView
{
CGRect frame = self.picChosenImageView.frame;
frame.origin = CGPointZero;
self.picChosenImageView.frame = frame;
//self.picChosenImageView.transform = prevTransform;
}
-(void) scrollViewWillBeginZooming:(UIScrollView *)scrollView withView:(UIView *)view
{
if(!isZoomingStarted)
{
self.picChosenImageView.transform = CGAffineTransformRotate(self.picChosenImageView.transform, angle);
NSLog(#"The zooming started");
isZoomingStarted = TRUE;
CGSize contentSize = self.containerView.bounds.size;
CGRect contentFrame = self.containerView.bounds;
NSLog(#"frame on start: %#", NSStringFromCGRect(contentFrame));
NSLog(#"size on start: %#", NSStringFromCGSize(contentSize));
//prevTransform = self.picChosenImageView.transform;
}
}
-(void) scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale
{
if(isZoomingStarted)
{
self.picChosenImageView.transform = CGAffineTransformRotate(self.picChosenImageView.transform, angle);
isZoomingStarted = FALSE;
CGSize contentSize = self.containerView.contentSize;
CGRect contentFrame = self.containerView.bounds;
NSLog(#"frame on end: %#", NSStringFromCGRect(contentFrame));
NSLog(#"size on end: %#", NSStringFromCGSize(contentSize));
}
}
#pragma mark - GestureRecognizer methods
- (void) handleRotate:(UIRotationGestureRecognizer *)recognizer
{
if(isZoomingStarted == FALSE)
{
if([recognizer state] == UIGestureRecognizerStateBegan)
{
angle = 0.0f;
[self lockZoom];
}
useRotation+= recognizer.rotation;
while( useRotation < -M_PI )
{
useRotation += M_PI*2;
}
while( useRotation > M_PI )
{
useRotation -= M_PI*2;
}
NSLog(#"The rotated value is %f",RADIANS_TO_DEGREES(useRotation));
self.picChosenImageView.transform = CGAffineTransformRotate([self.picChosenImageView transform],
recognizer.rotation);
[recognizer setRotation:0];
if([recognizer state] == UIGestureRecognizerStateEnded)
{
angle = useRotation;
useRotation = 0.0f;
isRotationStarted = FALSE;
self.containerView.hidden = NO;
//prevTransform = self.picChosenImageView.transform;
[self unlockZoom];
}
}
}
My problem is, I am able to successfully do a zoom in and zoom out. I am able to rotate the uiimageview as I wanted to. After rotating the uiimageview to a certain angle, and when I am trying to zoom in, the imageview gets back to the original position (rotate itself back to zero degree) and then the zooming happens. I want to retain the rotation and also zoom. I tried saving the previous transform and assign in back scrollDidzoom and scrollDidBegin delegate methods. None worked. Please help me to spot my mistake which I am overlooking.
try using CGAffineTransformScale instead of just resizing the frame for zooming:
anImage.transform = CGAffineTransformScale(anImage.transform, 2.0, 2.0);
changing the transform for scaling might fix your rotation issue.
hope this helps.
I had the same problem. UIScrollView is taking control over UIImageView and it is using transform without rotation.
So I do not give image reference to scroll and I have added UIPinchGestureRecognizer for scaling.
func viewForZoomingInScrollView(scrollView: UIScrollView) -> UIView? {
return nil
}
Dragging is still working :)
// viewDidLoad
var pinchGestureRecognizer = UIPinchGestureRecognizer(target: self, action: #selector(pinchRecogniezed))
scrollView.addGestureRecognizer(pinchGestureRecognizer)
func pinchRecogniezed(sender: UIPinchGestureRecognizer) {
if sender.state == .Began || sender.state == .Changed {
let scale = sender.scale
imageView.transform = CGAffineTransformScale(imageView.transform, scale, scale)
sender.scale = 1
}
}
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 delta from last state) and I can't figure out how to set a limit to the size/heigh of the object being zoomed.
-(void)scale:(id)sender {
[self.view bringSubviewToFront:[(UIPinchGestureRecognizer*)sender view]];
if([(UIPinchGestureRecognizer*)sender state] == UIGestureRecognizerStateEnded) {
lastScale = 1.0;
return;
}
CGFloat pinchscale = [(UIPinchGestureRecognizer*)sender scale];
CGFloat scale = 1.0 - (lastScale - pinchscale);
CGAffineTransform currentTransform = [(UIPinchGestureRecognizer*)sender view].transform;
CGAffineTransform holderTransform = holderView.transform;
CGAffineTransform newTransform = CGAffineTransformScale(currentTransform, scale, scale);
[[(UIPinchGestureRecognizer*)sender view] setTransform:newTransform];
lastScale = [(UIPinchGestureRecognizer*)sender scale];
}
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
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;
}
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
Method 1
gestureRecognizer.scale start with 1.0 at the beginning of pinch (gestureRecognizer.state == .began), and gestureRecognizer.scale in later state (.changed or .end) is always based on that, for example, if the view size is view_size at the beginning of pinch (might not be the same with the original size orig_view_size), gestureRecognizer.scale always starts with 1.0, and if it becomes 2.0 later, it's size will be 2 * view_size, so the scale always based on that when the pinch starts.
And we can get the scale at the beginning of pinch (gestureRecognizer.state == .began) lastScale = self.imageView.frame.width/self.imageView.bounds.size.width, so the scale of the original image now should be lastScale * gestureRecognizer.scale
lastScale: The scale of last round of Pinch, a round of Pinch is from state.start to state.end, and the scale is based on the original view size.
gestureRecognizer.scale: current scale, based on the view size after last round of Pinch.
currentScale: current scale, based on the orignial view size.
newScale: new scale, based on the orignial view size. newScale = lastScale * gestureRecognizer.scale, and you can limit the scale of the view by comparing the limitation with newScale.
```
var lastScale:CGFloat = 1.0
#objc func handlePinch(_ gestureRecognizer: UIPinchGestureRecognizer) {
var newScale = gestureRecognizer.scale
if gestureRecognizer.state == .began {
lastScale = self.imageView.frame.width/self.imageView.bounds.size.width
}
newScale = newScale * lastScale
if newScale < minScale {
newScale = minScale
} else if newScale > maxScale {
newScale = maxScale
}
let currentScale = self.imageView.frame.width/self.imageView.bounds.size.width
self.imageView.transform = CGAffineTransform(scaleX: newScale, y: newScale)
print("last Scale: \(lastScale), current scale: \(currentScale), new scale: \(newScale), gestureRecognizer.scale: \(gestureRecognizer.scale)")
}
```
Method 2
gestureRecognizer.scale start with 1.0 on each Pinch notification, this require you reset gestureRecognizer.scale = 1 in the code in the end of each notification handler, so now gestureRecognizer.scale is based on the view size of last Pinch notification, NOT based on the view size at the beginning of pinch. This is the most important difference with method 1. And since we don't rely on the scale of last round, we don't need lastScale anymore.
currentScale: current scale, based on the orignial view size.
gestureRecognizer.scale: new scale, based on the view size of last Pinch (not the last round), the scale value based on the orignial view size will be currentScale * gestureRecognizer.scale
And we use transform.scaledBy now, which use the scale based on view size of last Pinch (not the last round).
```
#objc func handlePinch(_ gestureRecognizer: UIPinchGestureRecognizer) {
let currentScale = self.imageView.frame.width/self.imageView.bounds.size.width
var newScale = gestureRecognizer.scale
if currentScale * gestureRecognizer.scale < minScale {
newScale = minScale / currentScale
} else if currentScale * gestureRecognizer.scale > maxScale {
newScale = maxScale / currentScale
}
self.imageView.transform = self.imageView.transform.scaledBy(x: newScale, y: newScale)
print("current scale: \(currentScale), new scale: \(newScale)")
gestureRecognizer.scale = 1
}
```
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;
}
}
- (void)handlePinch:(UIPinchGestureRecognizer *)recognizer{
//recognizer.scale=1;
CGFloat pinchScale = recognizer.scale;
pinchScale = round(pinchScale * 1000) / 1000.0;
NSLog(#"%lf",pinchScale);
if (pinchScale < 1)
{
currentLabel.font = [UIFont fontWithName:currentLabel.font.fontName size:
(currentLabel.font.pointSize - pinchScale)];
recognizer.view.transform = CGAffineTransformScale(recognizer.view.transform, recognizer.scale, recognizer.scale);
[currentLabel sizeToFit];
recognizer.scale=1;
}
else
{
currentLabel.font = [UIFont fontWithName:currentLabel.font.fontName size:(currentLabel.font.pointSize + pinchScale)];
recognizer.view.transform = CGAffineTransformScale(recognizer.view.transform, recognizer.scale, recognizer.scale);
[currentLabel sizeToFit];
recognizer.scale=1;
}
//currentLabel.adjustsFontSizeToFitWidth = YES;
// [currentLabel sizeToFit];
NSLog(#"Font :%#",label.font);
}
- (void)pinchToZoom:(UIPinchGestureRecognizer*)gesture
{
switch (gesture.state)
{
case UIGestureRecognizerStateBegan:
{
lastScale = gesture.scale;
}break;
case UIGestureRecognizerStateChanged:
{
const CGFloat zoomSensitivity = 5;
const CGFloat zoomMin = 1;
const CGFloat zoomMax = 16;
CGFloat objectScale = gesture.view.contentScaleFactor;
CGFloat zoomDiff = lastScale - gesture.scale;
CGFloat zoomDirty = objectScale - zoomDiff * zoomSensivity;
CGFloat zoomTo = fmaxf(zoomMin, fminf(zoomDirty, zoomMax));
// step round if needed (neutralize elusive changes)
zoomTo = (NSInteger)(zoomTo * 10) * 0.1;
if ( objectScale != zoomTo )
gesture.view.contentScaleFactor = zoomTo;
lastScale = gesture.scale;
}break;
default:
break;
}
}
I took #Paul Solt solution - that is great btw, and adapted it to Swift, for those interested
#objc func pinchUpdated(recognizer: UIPinchGestureRecognizer) {
if recognizer.state == .began {
// Reset the last scale, necessary if there are multiple objects with different scales
lastScale = recognizer.scale
}
if recognizer.state == .began || recognizer.state == .changed {
let currentScale = recognizer.view!.layer.value(forKeyPath: "transform.scale") as! CGFloat
// Constants to adjust the max/min values of zoom
let maxScale: CGFloat = 4.0
let ninScale: CGFloat = 0.9
var newScale: CGFloat = 1 - (lastScale - recognizer.scale)
newScale = min(newScale, maxScale / currentScale)
newScale = max(newScale, ninScale / currentScale)
recognizer.view!.transform = recognizer.view!.transform.scaledBy(x: newScale, y: newScale)
lastScale = recognizer.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
I have an array of Image URl's from that array i have displayed my Images in Scrollview because i want to take the advantage of Paging and zooming so what should i do to achieve this task?suppose my array contains 10 URL then by each time scrolling i should get different image and i could also performing Zooming-In and Zooming-Out any of the 10 Images so please give me some guidelines to achieve my task.Thanks in Advance.
Try this
- (void)setCurrentPage:(NSUInteger)page {
if (page == offsetX)
return;
offsetX = page;
// in a real app, this would be a good place to instantiate more view controllers -- see SDK examples
}
- (CGSize)pageSize {
CGSize pageSize = scrollView1.frame.size;
if (UIInterfaceOrientationIsLandscape(self.interfaceOrientation))
return CGSizeMake(pageSize.height, pageSize.width);
else
return pageSize;
}
- (void)setPagingMode {
// reposition pages side by side, add them back to the view
CGSize pageSize = [self pageSize];
NSUInteger page = 0;
for (UIView *view in imgViewArray) {
if (!view.superview)
[scrollView1 addSubview:view];
view.frame = CGRectMake(pageSize.width * page++, 0, pageSize.width, pageSize.height);
}
scrollView1.pagingEnabled = YES;
scrollView1.showsVerticalScrollIndicator = scrollView1.showsHorizontalScrollIndicator = NO;
scrollView1.contentSize = CGSizeMake(pageSize.width * [imgViewArray count], pageSize.height);
scrollView1.contentOffset = CGPointMake(pageSize.width * offsetX, 0);
scrollViewMode = ScrollViewModePaging;
}
- (void)setZoomingMode {
NSLog(#"setZoomingMode");
scrollViewMode = ScrollViewModeZooming; // has to be set early, or else currentPage will be mistakenly reset by scrollViewDidScroll
// hide all pages except the current one
NSUInteger page = 0;
for (UIView *view in imgViewArray)
if (offsetX != page++)
[view removeFromSuperview];
scrollView1.pagingEnabled = NO;
scrollView1.showsVerticalScrollIndicator = scrollView1.showsHorizontalScrollIndicator = YES;
pendingOffsetDelta = scrollView1.contentOffset.x;
scrollView1.bouncesZoom = YES;
}
- (void)scrollViewDidScroll:(UIScrollView *)aScrollView
{
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad)
{
// offsetX=(aScrollView.contentOffset.x)/768;
//NSLog(#"Page No:%d",offsetX);
if (scrollViewMode == ScrollViewModePaging)
[self setCurrentPage:roundf(scrollView1.contentOffset.x / [self pageSize].width)];
}
else
{
//offsetX=(aScrollView.contentOffset.x)/320;
//NSLog(#"Page No:%d",offsetX);
if (scrollViewMode == ScrollViewModePaging)
[self setCurrentPage:roundf(scrollView1.contentOffset.x / [self pageSize].width)];
}
}
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)aScrollView
{
NSLog(#"viewForZoomingInScrollView");
if (scrollViewMode != ScrollViewModeZooming)
[self setZoomingMode];
return [imgViewArray objectAtIndex:offsetX];
}
- (void)scrollViewDidEndZooming:(UIScrollView *)aScrollView withView:(UIView *)view atScale:(float)scale {
NSLog(#"scrollViewDidEndZooming");
if (scrollView1.zoomScale == scrollView1.minimumZoomScale)
[self setPagingMode];
else if (pendingOffsetDelta > 0) {
UIView *view = [imgViewArray objectAtIndex:offsetX];
view.center = CGPointMake(view.center.x - pendingOffsetDelta, view.center.y);
CGSize pageSize = [self pageSize];
scrollView1.contentOffset = CGPointMake(scrollView1.contentOffset.x - pendingOffsetDelta, scrollView1.contentOffset.y);
scrollView1.contentSize = CGSizeMake(pageSize.width * scrollView1.zoomScale, pageSize.height * scrollView1.zoomScale);
pendingOffsetDelta = 0;
}
}