I'm using UIPinchGestureRecognizer in my app to zoom in on a view (and yes, there's a reason I'm not using UIScrollView). When I pinch outwards with my fingers, the view zooms in as expected, and if I then reverse pinch without taking my fingers off the screen, it also zooms right. However, if I initiate the zoom by pinching inwards, the rate at which the view zooms is dramatically slower. I'm guessing this is because of how UIPinchGestureRecognizer works - the scale of the UIPinchGestureRecognizer is >1 when pinching outwards, and <1 when pinching inwards. Unfortunately, I do not know how to accurately reflect this in my code.
- (IBAction)didDetectPinchGesture:(id)sender {
UIPinchGestureRecognizer *gestureRecognizer = (UIPinchGestureRecognizer *)sender;
CGFloat scale = [gestureRecognizer scale];
switch ([gestureRecognizer state]) {
case UIGestureRecognizerStateBegan:
_lastScale = [gestureRecognizer scale];
break;
case UIGestureRecognizerStateChanged:
CGFloat currentScale = [[self.imageView.layer valueForKeyPath:#"transform.scale"] floatValue];
// Constants to adjust the max/min values of zoom
const CGFloat kMaxScale = 5.0;
const CGFloat kMinScale = 1.0;
CGFloat newScale = 1 - (_lastScale - scale); // new scale is in the range (0-1)
newScale = MIN(newScale, kMaxScale / currentScale);
newScale = MAX(newScale, kMinScale / currentScale);
NSLog(#"%f", newScale);
CGAffineTransform transform = CGAffineTransformScale([self.imageView transform], newScale, newScale);
self.imageView.transform = transform;
_lastScale = scale; // Store the previous scale factor for the next pinch gesture call
break;
default:
_lastScale = [gestureRecognizer scale];
break;
}
}
A very simple solution to this is to reset the gestureRecognizer scale back to 1 when you're finished:
...
default:
_lastScale = [gestureRecognizer scale];
// Add this:
[gestureRecognizer setScale:1];
break;
}
Related
I am fairly new to iOS and I am trying to figure out how to use a Pinch gesture to zoom with inertia and overhead (I do not know if the word overhead is correct in this context, in german it would be called "Überschwingen").
Basically what it shall do: It should have a max and min scale (in my case 1.0 to 4.0) in which you can zoom. When the gesture is finished it should take the given velocity and make a curve out animation, also allowing the view to over- and underflow the given scales and then move back to the min or max like with tension.
I got the Gesture Recognizer running for this and also managed to get it make use of my minimum and maximum scale (using examples from stackoverflow). This is what I got so far:
- (void)handle_pinch:(UIPinchGestureRecognizer *)recognizer
{
if([recognizer state] == UIGestureRecognizerStateBegan) {
previousScale = 1.0;
lastPoint = [recognizer locationInView:[recognizer view]];
}
if ([recognizer state] == UIGestureRecognizerStateChanged) {
CGFloat currentScale = [[[recognizer view].layer valueForKeyPath:#"transform.scale"] floatValue];
// Constants to adjust the max/min values of zoom
const CGFloat kMaxScale = 4.0;
const CGFloat kMinScale = 1.0;
CGFloat newScale = 1 - (previousScale - [recognizer scale]); // new scale is in the range (0-1)
newScale = MIN(newScale, kMaxScale / currentScale);
newScale = MAX(newScale, kMinScale / currentScale);
scale = newScale;
CGAffineTransform transform = CGAffineTransformScale([[recognizer view] transform], newScale, newScale);
[recognizer view].transform = transform;
CGPoint point = [recognizer locationInView:[recognizer view]];
CGAffineTransform transformTranslate = CGAffineTransformTranslate([[recognizer view] transform], point.x-lastPoint.x, point.y-lastPoint.y);
[recognizer view].transform = transformTranslate;
NSLog(#"Transformed");
}
}
But I do now know how I can add the animation here. Thanks for any help!
You should be using a UIScrollView to achieve the pinch to zoom effect as UIScrollView is already integrated along with animation. Just add your UIView inside a UIScrollView.
Here is great tutorial on UIScrollView. He is using a UIImageView but UIView will behave in a similar way.
https://www.raywenderlich.com/122139/uiscrollview-tutorial
I'm stuck on a case where I want to zooming a part(rect) of a scrollview programatically not by touch or gestures.
CGRect cropRect = CGRectMake(xPos * [UIScreen mainScreen].nativeScale, yPos * [UIScreen mainScreen].nativeScale, width1 * [UIScreen mainScreen].nativeScale, height1 * [UIScreen mainScreen].nativeScale);
[self.scrlVPhoto zoomToRect:cropRect animated:YES];
I've set delegate and also implemented delegate method.
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)aScrollView {
return self.containerView;
}
self.containerView is a view that I want to zoom.
I've tried everything and I'm still confused how to get out of this.
Please some one can help me. Any help is appreciated.
Thanks in advance.
I am going to answer my own question.
I've searched lots of articles and hours of debugging I've found that when scrollview is zoomed then actually its contentSize is increased its zoomScale remain unchanged.
So I just used transform property of the scrollview's subview (i.e. self.containerView) and set scrollview's contentOffset I got what I was looking for.
self.containerView.transform = CGAffineTransformMakeScale(1.8, 1.8); // containerView is subview of scrollview and 1.8 is zoom scale just for eg.
[self.scrlVPhoto setContentOffset:CGPointMake(self.scrlVPhoto.contentOffset.x, (self.scrlVPhoto.contentOffset.y + cropRect.origin.y)) animated:true];
You should add the view which you want to zoom inside the scrollview. Inside the delegate method return the view which you want to zoom.
I have Create the UIImageView, set the Zoom option in imageView when double tap or single tap to UIImageView and the code is given below,
viewDidLoad Method:
UITapGestureRecognizer *doubleTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(handleDoubleTap:)];
[doubleTap setNumberOfTapsRequired:2];
DoubleTab Method:
- (void)handleDoubleTap:(UIGestureRecognizer *)gestureRecognizer {
// zoom in
float newScale = [myscrollview zoomScale] * 2;
if (newScale > self.myscrollview.maximumZoomScale){
newScale = self.myscrollview.minimumZoomScale;
CGRect zoomRect = [self zoomRectForScale:newScale withCenter:[gestureRecognizer locationInView:gestureRecognizer.view]];
[myscrollview zoomToRect:zoomRect animated:YES];
}
else{
newScale = self.myscrollview.maximumZoomScale;
CGRect zoomRect = [self zoomRectForScale:newScale withCenter:[gestureRecognizer locationInView:gestureRecognizer.view]];
[myscrollview zoomToRect:zoomRect animated:YES];
}
}
- (CGRect)zoomRectForScale:(float)scale withCenter:(CGPoint)center {
CGRect zoomRect;
// the zoom rect is in the content view's coordinates.
// At a zoom scale of 1.0, it would be the size of the imageScrollView's bounds.
// As the zoom scale decreases, so more content is visible, the size of the rect grows.
zoomRect.size.height = [myscrollview frame].size.height / scale;
zoomRect.size.width = [myscrollview frame].size.width / scale;
// choose an origin so as to get the right center.
zoomRect.origin.x = center.x - (zoomRect.size.width / 2.0);
zoomRect.origin.y = center.y - (zoomRect.size.height / 2.0);
return zoomRect;
}
And you need the delegate methods are shown below,
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
if (vmov==nil)
{
float newwidth;
float newheight;
UIImage *image=detaimageview.image;
if (image.size.height>=image.size.width){
newheight=detaimageview.frame.size.height;
newwidth=(image.size.width/image.size.height)*newheight;
if(newwidth>detaimageview.frame.size.width){
float diff=detaimageview.frame.size.width-newwidth;
newheight=newheight+diff/newheight*newheight;
newwidth=detaimageview.frame.size.width;
}
}
else{
newwidth=detaimageview.frame.size.width;
newheight=(image.size.height/image.size.width)*newwidth;
if(newheight>detaimageview.frame.size.height){
float diff=detaimageview.frame.size.height-newheight;
newwidth=newwidth+diff/newwidth*newwidth;
newheight=detaimageview.frame.size.height;
myscrollview.clipsToBounds = NO;
}
}
CGRect frame;
CGFloat screenWidth;
CGFloat screenHeight;
CGRect screenRect = [[UIScreen mainScreen] bounds];
screenWidth = screenRect.size.width;
screenHeight = screenRect.size.height-64;
frame.size.width = newwidth;
frame.size.height = newheight;
self.myscrollview.contentSize = frame.size;
float x,y;
x=0;
y=0;
if (newheight<screenHeight)
{
x =screenHeight/2-newheight/2;
}
if (newwidth<screenWidth)
{
y =screenWidth/2-newwidth/2;
}
self.detaimageview.frame = CGRectMake(y,x,newwidth,newheight);
return self.detaimageview;
}
return nil;
}
this above codes are zoom the imageView and its working for my Projects,
hope its helpful
Please check this one. Its may be helpful to you
-(void)handlePinchWithGestureRecognizer:(UIPinchGestureRecognizer *)pinchGestureRecognizer{
//[self.superImage_View setAlpha:0.5];
if([pinchGestureRecognizer state] == UIGestureRecognizerStateBegan) {
// Reset the last scale, necessary if there are multiple objects with different scales
lastScale = [pinchGestureRecognizer scale];
}
if ([pinchGestureRecognizer state] == UIGestureRecognizerStateBegan ||
[pinchGestureRecognizer state] == UIGestureRecognizerStateChanged){
CGFloat currentScale = [[[pinchGestureRecognizer 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-[pinchGestureRecognizer scale]) ; // new scale is in the range (0-1)
newScale = MIN(newScale, kMaxScale / currentScale);
newScale = MAX(newScale, kMinScale / currentScale);
CGAffineTransform transform = CGAffineTransformScale([[pinchGestureRecognizer view] transform], newScale, newScale);
[pinchGestureRecognizer view].transform = transform;
lastScale = [pinchGestureRecognizer scale]; // Store the previous scale factor for the next pinch gesture call
}
if ([pinchGestureRecognizer state] == UIGestureRecognizerStateEnded) {
[undoList addObject:cropImage];
}
}
I need to increase and decrease GPUImagePinchDistortionFilter radius according to pinch, I am using pinch gesture recognizer and storing the starting scale of the gesture recognizer. I need to calculate the pinch scale between -2.0 and 2.0. Here is my code.
if([gestureRecognizer state] == UIGestureRecognizerStateBegan) {
lastScale = [gestureRecognizer scale];
}
if ([gestureRecognizer state] == UIGestureRecognizerStateBegan ||
[gestureRecognizer state] == UIGestureRecognizerStateChanged) {
CGFloat currentScale = [[[gestureRecognizer view].layer valueForKeyPath:#"transform.scale"] floatValue];
CGFloat kMaxScale = 2.0;
CGFloat kMinScale = -2.0;
CGFloat newScale = 1.0 - (lastScale - [gestureRecognizer scale]);
newScale = MIN(newScale, kMaxScale / currentScale);
newScale = MAX(newScale, kMinScale / currentScale);
[pinchDistortionFilter setRadius:newScale];
}
I have followed some posts to do this. but still stuck, also I need to set this every time the user pinches but the radius must be within the min and max. actually I am confused how to achieve this, can anyone help me with this ?
Thank You.
I think you should try to save the lastScale and radius too in correct place, and the radius should be > 0 see my answer below for my implementation, it might help you.
float currentApplicableScale = (gestureRecognizer.scale/2);
if (gestureRecognizer.state == UIGestureRecognizerStateEnded) {
lastRadius = lastScale;
}else if (gestureRecognizer.state==UIGestureRecognizerStateBegan) {
firstScale = currentApplicableScale;
}else if (gestureRecognizer.state == UIGestureRecognizerStateChanged) {
float radius = currentApplicableScale;
if (lastRadius<=0) {
lastRadius = 0.25;
}
radius = radius - (firstScale - lastRadius);
if (radius>0 && radius < 0.5) {
[(GPUImagePinchDistortionFilter*)pinchFilter setRadius:radius];
lastScale = radius; //Save here
}
}
I'm getting images from remote server and displaying in UIImageView then doing pinch gesture to this imageview. But when i pinching image, i'm getting image stretching. It's loosing original resolution and quality.
mmageView=[[UIImageView alloc]initWithFrame:CGRectMake(50,50,150,150)];
[self.view addSubview:mmageView];
UIPinchGestureRecognizer *dbpinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:#selector(dbhandlePinch:)];
[mmageView addGestureRecognizer:dbpinchGesture];
UIPinchGesture:
-(void)dbhandlePinch:(UIPinchGestureRecognizer*)recognizer {
if([recognizer state] == UIGestureRecognizerStateBegan) {
// Reset the last scale, necessary if there are multiple objects with different scales
LastScale = [recognizer scale];
}
if ([recognizer state] == UIGestureRecognizerStateBegan ||
[recognizer state] == UIGestureRecognizerStateChanged) {
CGFloat currentScale = [[[recognizer view].layer valueForKeyPath:#"transform.scale"] floatValue];
// Constants to adjust the max/min values of zoom
// const CGFloat kMaxScale = 2.0;
const CGFloat kMinScale = 0.8;
CGFloat newScale = 1 - (LastScale - [recognizer scale]);
// newScale = MIN(newScale, kMaxScale / currentScale);
newScale = MAX(newScale, kMinScale / currentScale);
CGAffineTransform transform = CGAffineTransformScale([[recognizer view] transform], newScale, newScale);
[recognizer view].transform = transform;
LastScale = [recognizer scale]; // Store the previous scale factor for the next pinch gesture call
}
}
For pinch zoom add your imageView in a scrollView and Import UIScrollViewDelegate
- (void)viewDidLoad
{
[super viewDidLoad];
//for pinch gesture
_scrollView.minimumZoomScale = 0.5;
_scrollView.maximumZoomScale = 6.0;
_scrollView.contentSize = CGSizeMake(_imageView.frame.size.width, _imageView.frame.size.height);
_scrollView.delegate = self;
}
-(UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
return _imageView;
}
UIScrollView makes supporting the pinch gestures for zooming easy. Better solution in Apple Documentation.
I've created a UIGestureRecognizer much like this one:
- (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
}
}
This works as expected, however my client wants it to be less sensitive to touch. How can I reduce the velocity of the pinching (both inward and outward) so that it zooms at about 80% the default velocity?
Did you tried to scale the current value to the 80%.
if ([gestureRecognizer state] == UIGestureRecognizerStateBegan ||
[gestureRecognizer state] == UIGestureRecognizerStateChanged) {
CGFloat maxScale = 2.0;
CGFloat currentScale = [gestureRecognizer scale];
currentScale = 0.8 * currentScale; //80% of scaling
if(currentScale < 0.8)
currentScale = 0.8;
if(currentScale > maxScale)
currentScale = maxScale;
[gestureRecognizer view].transform = CGAffineTransformScale([[gestureRecognizer view] transform], currentScale, currentScale);
}
Try this.
- (void)handlePinching:(UIPinchGestureRecognizer *)gestureRecognizer {
if([gestureRecognizer state] == UIGestureRecognizerStateBegan) {
if (CGRectIsEmpty(self.initalFrame)) {
self.initalFrame = gestureRecognizer.view.frame;
// store view's original frame and never change it
}
if (self.preScale == 0.f) {
self.preScale = 1.f;
}
gestureRecognizer.scale = self.preScale;
// gestureRecognizer and view should share the same scale state at the beginning
}
if ([gestureRecognizer state] == UIGestureRecognizerStateBegan ||
[gestureRecognizer state] == UIGestureRecognizerStateChanged) {
const CGFloat kMaxScale = 2.0;
const CGFloat kMinScale = 1.0;
CGFloat newScale = gestureRecognizer.scale;
newScale = (newScale - self.preScale) * 0.8 + self.preScale;
newScale = MIN(newScale, kMaxScale);
newScale = MAX(newScale, kMinScale);
CGRect newFrame = self.initalFrame;
newFrame.size.height *= newScale;
newFrame.size.width *= newScale;
gestureRecognizer.view.frame = newFrame;
self.preScale = newScale;
}
}
The points are
use frame to implement scale.
do change to the scale variable's change to slow/speed scaling.
Turns out the excessive speed of the zoom was actually a bug in the linked answer's code. Sadly this doesn't actually answer my actual question though (for which I'd still like an answer!), but it does serve to solve my client's problem.
Note the added line at the bottom that resets the gestureRecognizer back to 1:
- (void)handlePinchGesture:(UIPinchGestureRecognizer *)gestureRecognizer {
...
lastScale = [gestureRecognizer scale]; // Store the previous scale factor for the next pinch gesture call
gestureRecognizer.scale = 1;
// ^ added this line
}
}