Using UIPinchGestureRecognizer to scale uiviews in one direction

I would like to know how we can use the UIPinchGestureRecognizer to scale the UIView only single (x or y) directions. Say, if the user moves two fingers with a hard gesture in only one direction (in the horizontal direction), only the width of the uiview should increase / decrease, and if the fingers move only vertically, the height should change. If the fingers move diagonally, then the height and width of the uiview should increase / decrease. I saw Apple's MoveMe sample code.

 UIPinchGestureRecognizer *pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(scalePiece:)]; [pinchGesture setDelegate:self]; [piece addGestureRecognizer:pinchGesture]; [pinchGesture release]; 

Scale:

 - (void)scalePiece:(UIPinchGestureRecognizer *)gestureRecognizer { UIView *piece = (UIView *) [gestureRecognizer view]; NSLog(@"scalePiece enter"); if ([gestureRecognizer state] == UIGestureRecognizerStateBegan){ NSLog(@"inside if"); lastTouchPosition = [gestureRecognizer locationInView:piece]; } else if ([gestureRecognizer state] == UIGestureRecognizerStateBegan || [gestureRecognizer state] == UIGestureRecognizerStateChanged){ NSLog(@"inside else"); CGPoint currentTouchLocation = [gestureRecognizer locationInView:piece]; NSLog(@"currentTouchLocation = %@ and lastTouchPosition= %@",NSStringFromCGPoint(currentTouchLocation), NSStringFromCGPoint(lastTouchPosition)); CGPoint deltaMove = [self calculatePointDistancewithPoint1:currentTouchLocation andPoint2:lastTouchPosition]; NSLog(@"deltaMove = %@",NSStringFromCGPoint(deltaMove)); float distance = sqrt(deltaMove.x*deltaMove.x + deltaMove.y*deltaMove.y); NSLog(@"distance = %f",distance); float hScale = 1 - deltaMove.x/distance * (1-gestureRecognizer.scale); float vScale = 1 - deltaMove.y/distance * (1-gestureRecognizer.scale); if (distance == 0) { hScale = 1; vScale = 1; } NSLog(@"[gestureRecognizer scale] = %f",[gestureRecognizer scale]); NSLog(@"hScale = %f and vScale = %f",hScale, vScale); piece.transform = CGAffineTransformScale([piece transform], hScale, vScale); [gestureRecognizer setScale:1]; lastTouchPosition = currentTouchLocation; } NSLog(@"scalePiece exit"); } 

Calculate distance:

 - (CGPoint) calculatePointDistancewithPoint1:(CGPoint)point1 andPoint2:(CGPoint) point2 { return CGPointMake(point2.x - point1.x, point2.y - point1.y); } 

This is the log-out when I try to pinch (enlarge) the view only by moving my fingers in the vertical direction. Element height does not increase.

 2011-07-21 13:06:56.245 New[8169:707] scalePiece enter 2011-07-21 13:06:56.248 New[8169:707] inside if 2011-07-21 13:06:56.251 New[8169:707] scalePiece exit 2011-07-21 13:06:56.259 New[8169:707] scalePiece enter 2011-07-21 13:06:56.262 New[8169:707] inside else 2011-07-21 13:06:56.264 New[8169:707] currentTouchLocation = {88, 87} and lastTouchPosition= {87, 86} 2011-07-21 13:06:56.265 New[8169:707] deltaMove = {-1, -1} 2011-07-21 13:06:56.267 New[8169:707] distance = 1.414214 2011-07-21 13:06:56.268 New[8169:707] [gestureRecognizer scale] = 1.102590 2011-07-21 13:06:56.271 New[8169:707] hScale = 0.927458 and vScale = 0.927458 2011-07-21 13:06:56.272 New[8169:707] scalePiece exit 2011-07-21 13:06:56.281 New[8169:707] scalePiece enter 2011-07-21 13:06:56.283 New[8169:707] inside else 2011-07-21 13:06:56.284 New[8169:707] currentTouchLocation = {87, 89} and lastTouchPosition= {88, 87} 2011-07-21 13:06:56.286 New[8169:707] deltaMove = {1, -2} 2011-07-21 13:06:56.287 New[8169:707] distance = 2.236068 2011-07-21 13:06:56.296 New[8169:707] [gestureRecognizer scale] = 1.096172 2011-07-21 13:06:56.298 New[8169:707] hScale = 1.043009 and vScale = 0.913981 2011-07-21 13:06:56.299 New[8169:707] scalePiece exit 2011-07-21 13:06:56.302 New[8169:707] scalePiece enter 2011-07-21 13:06:56.303 New[8169:707] inside else 2011-07-21 13:06:56.305 New[8169:707] currentTouchLocation = {88, 89} and lastTouchPosition= {87, 89} 2011-07-21 13:06:56.309 New[8169:707] deltaMove = {-1, 0} 2011-07-21 13:06:56.311 New[8169:707] distance = 1.000000 2011-07-21 13:06:56.313 New[8169:707] [gestureRecognizer scale] = 1.066320 2011-07-21 13:06:56.314 New[8169:707] hScale = 0.933680 and vScale = 1.000000 2011-07-21 13:06:56.316 New[8169:707] scalePiece exit 2011-07-21 13:06:56.318 New[8169:707] scalePiece enter 2011-07-21 13:06:56.320 New[8169:707] inside else 2011-07-21 13:06:56.329 New[8169:707] currentTouchLocation = {88, 90} and lastTouchPosition= {88, 89} 2011-07-21 13:06:56.331 New[8169:707] deltaMove = {0, -1} 2011-07-21 13:06:56.333 New[8169:707] distance = 1.000000 2011-07-21 13:06:56.334 New[8169:707] [gestureRecognizer scale] = 1.061696 2011-07-21 13:06:56.335 New[8169:707] hScale = 1.000000 and vScale = 0.938304 2011-07-21 13:06:56.338 New[8169:707] scalePiece exit 2011-07-21 13:06:56.343 New[8169:707] scalePiece enter 2011-07-21 13:06:56.346 New[8169:707] inside else 2011-07-21 13:06:56.347 New[8169:707] currentTouchLocation = {88, 92} and lastTouchPosition= {88, 90} 2011-07-21 13:06:56.349 New[8169:707] deltaMove = {0, -2} 2011-07-21 13:06:56.350 New[8169:707] distance = 2.000000 2011-07-21 13:06:56.351 New[8169:707] [gestureRecognizer scale] = 1.096869 2011-07-21 13:06:56.353 New[8169:707] hScale = 1.000000 and vScale = 0.903131 2011-07-21 13:06:56.362 New[8169:707] scalePiece exit 2011-07-21 13:06:56.366 New[8169:707] scalePiece enter 2011-07-21 13:06:56.370 New[8169:707] inside else 2011-07-21 13:06:56.373 New[8169:707] currentTouchLocation = {88, 92} and lastTouchPosition= {88, 92} 2011-07-21 13:06:56.376 New[8169:707] deltaMove = {0, 0} 2011-07-21 13:06:56.380 New[8169:707] distance = 0.000000 2011-07-21 13:06:56.383 New[8169:707] [gestureRecognizer scale] = 1.035330 2011-07-21 13:06:56.387 New[8169:707] hScale = 1.000000 and vScale = 1.000000 2011-07-21 13:06:56.389 New[8169:707] scalePiece exit 2011-07-21 13:06:56.393 New[8169:707] scalePiece enter 2011-07-21 13:06:56.397 New[8169:707] inside else 2011-07-21 13:06:56.399 New[8169:707] currentTouchLocation = {88, 93} and lastTouchPosition= {88, 92} 2011-07-21 13:06:56.403 New[8169:707] deltaMove = {0, -1} 2011-07-21 13:06:56.406 New[8169:707] distance = 1.000000 2011-07-21 13:06:56.409 New[8169:707] [gestureRecognizer scale] = 1.042659 2011-07-21 13:06:56.412 New[8169:707] hScale = 1.000000 and vScale = 0.957341 2011-07-21 13:06:56.414 New[8169:707] scalePiece exit 2011-07-21 13:06:56.419 New[8169:707] scalePiece enter 2011-07-21 13:06:56.422 New[8169:707] inside else 2011-07-21 13:06:56.425 New[8169:707] currentTouchLocation = {88, 92} and lastTouchPosition= {88, 93} 2011-07-21 13:06:56.427 New[8169:707] deltaMove = {0, 1} 2011-07-21 13:06:56.430 New[8169:707] distance = 1.000000 2011-07-21 13:06:56.432 New[8169:707] [gestureRecognizer scale] = 1.024549 2011-07-21 13:06:56.436 New[8169:707] hScale = 1.000000 and vScale = 1.024549 2011-07-21 13:06:56.439 New[8169:707] scalePiece exit 2011-07-21 13:06:56.442 New[8169:707] scalePiece enter 2011-07-21 13:06:56.447 New[8169:707] inside else 2011-07-21 13:06:56.450 New[8169:707] currentTouchLocation = {88, 92} and lastTouchPosition= {88, 92} 2011-07-21 13:06:56.453 New[8169:707] deltaMove = {0, 0} 2011-07-21 13:06:56.455 New[8169:707] distance = 0.000000 2011-07-21 13:06:56.458 New[8169:707] [gestureRecognizer scale] = 1.007702 2011-07-21 13:06:56.460 New[8169:707] hScale = 1.000000 and vScale = 1.000000 2011-07-21 13:06:56.464 New[8169:707] scalePiece exit 2011-07-21 13:06:56.501 New[8169:707] scalePiece enter 2011-07-21 13:06:56.504 New[8169:707] inside else 2011-07-21 13:06:56.507 New[8169:707] currentTouchLocation = {89, 92} and lastTouchPosition= {88, 92} 2011-07-21 13:06:56.509 New[8169:707] deltaMove = {-1, 0} 2011-07-21 13:06:56.510 New[8169:707] distance = 1.000000 2011-07-21 13:06:56.511 New[8169:707] [gestureRecognizer scale] = 1.000283 2011-07-21 13:06:56.513 New[8169:707] hScale = 0.999717 and vScale = 1.000000 2011-07-21 13:06:56.517 New[8169:707] scalePiece exit 2011-07-21 13:06:56.566 New[8169:707] scalePiece enter 2011-07-21 13:06:56.570 New[8169:707] inside else 2011-07-21 13:06:56.572 New[8169:707] currentTouchLocation = {89, 91} and lastTouchPosition= {89, 92} 2011-07-21 13:06:56.573 New[8169:707] deltaMove = {0, 1} 2011-07-21 13:06:56.575 New[8169:707] distance = 1.000000 2011-07-21 13:06:56.576 New[8169:707] [gestureRecognizer scale] = 1.008267 2011-07-21 13:06:56.579 New[8169:707] hScale = 1.000000 and vScale = 1.008267 2011-07-21 13:06:56.582 New[8169:707] scalePiece exit 2011-07-21 13:06:56.585 New[8169:707] scalePiece enter 2011-07-21 13:06:56.586 New[8169:707] inside else 2011-07-21 13:06:56.588 New[8169:707] currentTouchLocation = {89, 91} and lastTouchPosition= {89, 91} 2011-07-21 13:06:56.589 New[8169:707] deltaMove = {0, 0} 2011-07-21 13:06:56.591 New[8169:707] distance = 0.000000 2011-07-21 13:06:56.597 New[8169:707] [gestureRecognizer scale] = 1.000000 2011-07-21 13:06:56.599 New[8169:707] hScale = 1.000000 and vScale = 1.000000 2011-07-21 13:06:56.600 New[8169:707] scalePiece exit 2011-07-21 13:06:56.603 New[8169:707] scalePiece enter 2011-07-21 13:06:56.604 New[8169:707] inside else 2011-07-21 13:06:56.606 New[8169:707] currentTouchLocation = {89, 182} and lastTouchPosition= {89, 91} 2011-07-21 13:06:56.607 New[8169:707] deltaMove = {0, -91} 2011-07-21 13:06:56.617 New[8169:707] distance = 91.000000 2011-07-21 13:06:56.620 New[8169:707] [gestureRecognizer scale] = 1.000000 2011-07-21 13:06:56.623 New[8169:707] hScale = 1.000000 and vScale = 1.000000 2011-07-21 13:06:56.626 New[8169:707] scalePiece exit 2011-07-21 13:06:56.630 New[8169:707] scalePiece enter 2011-07-21 13:06:56.632 New[8169:707] scalePiece exit 
+6
source share
3 answers

If I understand your question correctly, you are aiming for disproportionate scaling along the horizontal and vertical axis. In this case, it comes down to providing your affinity transformation:

  piece.transform = CGAffineTransformScale([piece transform], hScale, vScale); 

with various scale factors.

One way to calculate them is as follows:

  • Define ivar in your class to hold lastTouchPosition ;

  • in your gesture handler you will do something like this:

     if ([gestureRecognizer state] == UIGestureRecognizerStateBegan){ lastTouchPosition = [gestureRecognize locationInView:yourViewHere]; hScale = 1; vScale = 1; } else if ([gestureRecognizer state] == UIGestureRecognizerStateBegan || [gestureRecognizer state] == UIGestureRecognizerStateChanged){ CGPoint currentTouchLocation = [gestureRecognize locationInView:yourViewHere]; CGPoint deltaMove = CGPointDistance(currentTouchLocation, lastTouchPosition); float distance = sqrt(deltaMove.x*deltaMove.x + deltaMove.y*deltaMove.y); hScale -= abs(deltaMove.x)/distance * (1-gestureRecognizer.scale); vScale -= abs(deltaMove.y)/distance * (1-gestureRecognizer.scale); piece.transform = CGAffineTransformScale([piece transform], hScale, vScale); [gestureRecognizer setScale:1]; lastTouchPosition = currentTouchLocation; } 

Alternative way:

  if ([gestureRecognizer state] == UIGestureRecognizerStateBegan){ lastTouchPosition = [gestureRecognize locationInView:yourViewHere]; } else if ([gestureRecognizer state] == UIGestureRecognizerStateBegan || [gestureRecognizer state] == UIGestureRecognizerStateChanged){ CGPoint currentTouchLocation = [gestureRecognize locationInView:yourViewHere]; CGPoint deltaMove = CGPointDistance(currentTouchLocation, lastTouchPosition); float distance = sqrt(deltaMove.x*deltaMove.x + deltaMove.y*deltaMove.y); float hScale = 1 - abs(deltaMove.x)/distance * (1-gestureRecognizer.scale); float vScale = 1 - abs(deltaMove.y)/distance * (1-gestureRecognizer.scale); piece.transform = CGAffineTransformScale([piece transform], hScale, vScale); lastTouchPosition = currentTouchLocation; } 

This will not save the current hFloat and vFloat at each iteration, and instead rely on the fact that gestureRecognizer will accumulate overall zooming. It performs an “absolute” calculation, while the first implementation performs a “relative” one.

Note that you also need to define CGPointDistance to calculate the distance between two touches and choose which view you want to use to calculate the distance ( yourViewHere ).

EDIT:

 CGPoint CGPointDistance(CGPoint point1, CGPoint point2) { return = CGPointMake(point2.x - point1.x, point2.y - point1.y); }; 

EDIT2: About the formula for calculating scaling

The idea calculates the change in the scale factor and applies it to the two directions (x and y) in accordance with their relative variations.

  • Delta scale factor: 1-gestureRecognizer.scale ;

  • the delta is multiplied by a factor so that it is somehow proportional to the offset along the horizontal or vertical axis; when the offset is zero, the delta scale is also zero; when the displacement is equal along two axes, the delta of the scale is also equal along two axes; I decided to divide by distance , but there are other possibilities (for example, dividing by the sum of two deltaMoves or max deltaMoves, etc.).

  • the adjusted delta is finally subtracted from the current scale of the element.

+8
source

Today I faced the same problem and I found a simple and short way to do this

 - (IBAction)handlePinch:(UIPinchGestureRecognizer *)recognizer { recognizer.view.transform = CGAffineTransformScale(recognizer.view.transform, recognizer.scale,1); recognizer.scale = 1; } 
+5
source

I created a custom version of UIPinchGestureRecognizer to do exactly what you are looking for. It uses the slope of the line between two fingers to determine the direction of the scale. He makes 3 types: Vertical; Horizontal; and combined (diagonal). Please see my notes below.

 -(void) scaleTheView:(UIPinchGestureRecognizer *)pinchRecognizer { if ([pinchRecognizer state] == UIGestureRecognizerStateBegan || [pinchRecognizer state] == UIGestureRecognizerStateChanged) { if ([pinchRecognizer numberOfTouches] > 1) { UIView *theView = [pinchRecognizer view]; CGPoint locationOne = [pinchRecognizer locationOfTouch:0 inView:theView]; CGPoint locationTwo = [pinchRecognizer locationOfTouch:1 inView:theView]; NSLog(@"touch ONE = %f, %f", locationOne.x, locationOne.y); NSLog(@"touch TWO = %f, %f", locationTwo.x, locationTwo.y); [scalableView setBackgroundColor:[UIColor redColor]]; if (locationOne.x == locationTwo.x) { // perfect vertical line // not likely, but to avoid dividing by 0 in the slope equation theSlope = 1000.0; }else if (locationOne.y == locationTwo.y) { // perfect horz line // not likely, but to avoid any problems in the slope equation theSlope = 0.0; }else { theSlope = (locationTwo.y - locationOne.y)/(locationTwo.x - locationOne.x); } double abSlope = ABS(theSlope); if (abSlope < 0.5) { // Horizontal pinch - scale in the X [arrows setImage:[UIImage imageNamed:@"HorzArrows.png"]]; arrows.hidden = FALSE; // tranform.a = X-axis NSLog(@"transform.A = %f", scalableView.transform.a); // tranform.d = Y-axis NSLog(@"transform.D = %f", scalableView.transform.d); // if hit scale limit along X-axis then stop scale and show Blocked image if (((pinchRecognizer.scale > 1.0) && (scalableView.transform.a >= 2.0)) || ((pinchRecognizer.scale < 1.0) && (scalableView.transform.a <= 0.1))) { blocked.hidden = FALSE; arrows.hidden = TRUE; } else { // scale along X-axis scalableView.transform = CGAffineTransformScale(scalableView.transform, pinchRecognizer.scale, 1.0); pinchRecognizer.scale = 1.0; blocked.hidden = TRUE; arrows.hidden = FALSE; } }else if (abSlope > 1.7) { // Vertical pinch - scale in the Y [arrows setImage:[UIImage imageNamed:@"VerticalArrows.png"]]; arrows.hidden = FALSE; NSLog(@"transform.A = %f", scalableView.transform.a); NSLog(@"transform.D = %f", scalableView.transform.d); // if hit scale limit along Y-axis then don't scale and show Blocked image if (((pinchRecognizer.scale > 1.0) && (scalableView.transform.d >= 2.0)) || ((pinchRecognizer.scale < 1.0) && (scalableView.transform.d <= 0.1))) { blocked.hidden = FALSE; arrows.hidden = TRUE; } else { // scale along Y-axis scalableView.transform = CGAffineTransformScale(scalableView.transform, 1.0, pinchRecognizer.scale); pinchRecognizer.scale = 1.0; blocked.hidden = TRUE; arrows.hidden = FALSE; } } else { // Diagonal pinch - scale in both directions [arrows setImage:[UIImage imageNamed:@"CrossArrows.png"]]; blocked.hidden = TRUE; arrows.hidden = FALSE; NSLog(@"transform.A = %f", scalableView.transform.a); NSLog(@"transform.D = %f", scalableView.transform.d); // if we have hit any limit don't allow scaling if ((((pinchRecognizer.scale > 1.0) && (scalableView.transform.a >= 2.0)) || ((pinchRecognizer.scale < 1.0) && (scalableView.transform.a <= 0.1))) || (((pinchRecognizer.scale > 1.0) && (scalableView.transform.d >= 2.0)) || ((pinchRecognizer.scale < 1.0) && (scalableView.transform.d <= 0.1)))) { blocked.hidden = FALSE; arrows.hidden = TRUE; } else { // scale in both directions scalableView.transform = CGAffineTransformScale(scalableView.transform, pinchRecognizer.scale, pinchRecognizer.scale); pinchRecognizer.scale = 1.0; blocked.hidden = TRUE; arrows.hidden = FALSE; } } // else for diagonal pinch } // if numberOfTouches } // StateBegan if if ([pinchRecognizer state] == UIGestureRecognizerStateEnded || [pinchRecognizer state] == UIGestureRecognizerStateCancelled) { NSLog(@"StateEnded StateCancelled"); [scalableView setBackgroundColor:[UIColor whiteColor]]; arrows.hidden = TRUE; blocked.hidden = TRUE; } } 

Remember to add the protocol to the view controller header file:

 @interface WhiteViewController : UIViewController <UIGestureRecognizerDelegate> { IBOutlet UIView *scalableView; IBOutlet UIView *mainView; IBOutlet UIImageView *arrows; IBOutlet UIImageView *blocked; } @property (strong, nonatomic) IBOutlet UIView *scalableView; @property (strong, nonatomic) IBOutlet UIView *mainView; @property (strong, nonatomic)IBOutlet UIImageView *arrows; @property (strong, nonatomic)IBOutlet UIImageView *blocked; -(void) scaleTheView:(UIPinchGestureRecognizer *)pinchRecognizer; @end 

And add a recognizer to viewDidLoad:

 - (void)viewDidLoad { UIPinchGestureRecognizer *pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(scaleTheView:)]; [pinchGesture setDelegate:self]; [mainView addGestureRecognizer:pinchGesture]; arrows.hidden = TRUE; blocked.hidden = TRUE; [scalableView setBackgroundColor:[UIColor whiteColor]];} 

This parameter is intended to use the main view to capture the pinch; and manipulate the second view. That way you can still scale it as the view gets small. You can modify it to respond directly to a scalable view.

LIMITATIONS: I arbitrarily chose the initial size of my view, so a scale limit of 2.0 would equal a full screen. My bottom scale is set to 0.1.

USER INTERACTION: I deal with a lot of user actions, such as changing the background color of the view and adding / changing arrows above the view to show the direction. It is very important to give them feedback during the scaling process, especially when changing directions like these codes.

Error: There is an error in Apple UIPinchGestureRecognizer. It registers the UIGestureRecognizerStateBegan with one click of 2 fingers, as you would expect. But as soon as in StateBegan or StateChanged you can raise one finger, and the state remains. It does not move to StateEnded or StateCancelled until both fingers are raised. This created a bug in my code and a lot of headaches! If numberOfTouches> 1 corrects this.

FUTURE: you can change the tilt settings on the scale in only one direction or just 2. If you add arrow images, you can see their change when you turn your fingers.

+2
source

Source: https://habr.com/ru/post/893135/


All Articles