Usando UIPinchGestureRecognizer para escalar uiviews en una sola dirección

Me gustaría saber cómo podemos utilizar UIPinchGestureRecognizer para escalar UIView en UIView simples (x o y) por sí solas. Digamos que si el usuario mueve sus dos dedos en un gesto de pellizco solo en una sola dirección (horizontal), solo debería boost / disminuir el ancho de la uiview y si los dedos se mueven solo verticalmente, la altura debería cambiar. Si los dedos se mueven diagonalmente, tanto la altura como el ancho de uiview deberían boost / disminuir. He visto el código de ejemplo MoveMe de Apple.

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

Pieza de escala:

 - (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"); } 

Calcular la distancia:

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

Esta es la salida del logging cuando trato de pellizcar (acercar) la vista solo moviendo los dedos en una dirección vertical. La altura del elemento no aumenta.

 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 

Si entiendo correctamente su pregunta, tiene como objective tener una escala no proporcional a lo largo del eje horizontal y vertical. En este caso, la cosa se networkinguce a proporcionar su transformación afín:

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

con diferentes factores de escala.

Una forma de calcularlos es la siguiente:

  1. defina un ivar en su class para almacenar la lastTouchPosition ;

  2. en tu manejador de gestos harás algo como esto:

     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; } 

Una forma alternativa de hacer es:

  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; } 

Esto no almacenará en cada iteración el hFloat y vFloat y en cambio se basará en el hecho de que gestureRecognizer acumulará el cambio de escala general. Realiza un cálculo "absoluto", mientras que la primera implementación es "relativa".

Tenga en count que también necesita definir CGPointDistance para calcular la distancia entre los dos toques y elegir qué vista va a usar para calcular la distancia ( yourViewHere ).

EDITAR:

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

EDIT2: sobre la fórmula para calcular la escala

La idea es calcular la variación en el factor de escala y aplicarlo a las dos direcciones (x e y) de acuerdo con sus variaciones relativas.

  1. el factor de escala delta es: 1-gestureRecognizer.scale ;

  2. el delta es el multiplicado por un factor, de modo que es de alguna manera proporcional al desplazamiento a lo largo del eje horizontal o vertical; cuando el desplazamiento es cero, la escala delta también es cero; cuando el desplazamiento es igual a lo largo de los dos ejes, la escala delta también es igual a lo largo de los dos ejes; Decidí dividir por la distance , pero hay otras posibilidades (como dividir por la sum de las dos deltaMoves o por el máximo de las deltaMoves, etc.).

  3. el delta ajustado se resta finalmente de la escala actual del elemento.

Hoy estoy enfrentando el mismo problema y encontré una manera simple y breve de hacer esto

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

Creé una versión personalizada de un UIPinchGestureRecognizer para hacer exactamente lo que estás buscando. Utiliza la pendiente de la línea entre los dos dedos para determinar la dirección de la escala. Realiza 3 types: vertical; Horizontal; y combinado (diagonal). Por favor, vea mis notas en la parte inferior.

 -(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 networkingColor]]; 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; } } 

Recuerde agregar el protocolo al file de encabezado del controller de vista:

 @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 

Y agregue el reconocedor en 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]];} 

Esto está configurado para usar la vista principal para capturar el pellizco; y manipular una segunda vista. De esta forma, puede escalarlo a medida que la vista se vuelva pequeña. Puede cambiarlo para que reaccione directamente a la vista escalable.

LÍMITES: elegí arbitrariamente el tamaño inicial de mi vista, por lo que un límite de escala de 2.0 sería igual a la pantalla completa. Mi escala más baja se establece en 0.1.

INTERACCIÓN DE USUARIO: Me meto con muchas cosas de la interacción del usuario, como cambiar el color de background de la vista y agregar / cambiar flechas sobre la vista para mostrar la dirección. Es importante darles retroalimentación durante el process de escalamiento, especialmente cuando se cambian las instrucciones como lo permite este código.

ERROR: Hay un error en el UIPinchGestureRecognizer de Apple. Registra UIGestureRecognizerStateBegan con el toque de 2 dedos como era de esperar. Pero una vez que está en StateBegan o StateChanged puede levantar un dedo y el estado permanece. No se mueve a StateEnded o StateCancelled hasta que ambos dedos se levanten. ¡Esto creó un error en mi código y muchos dolores de cabeza! El número ifOfTouches> 1 lo corrige.

FUTURO: Puede cambiar la configuration de pendiente para escalar en una sola dirección, o solo 2. Si agrega las imágenes de flechas, puede ver cómo cambian al girar los dedos.