cgaffinetransformrotate iphone ipad ios cgaffinetransform

iphone - cgaffinetransformrotate swift 3



Escala máxima/mínima de pellizco Zoom en UIPinchGestureRecognizer-iPhone iOS (9)

¿Cómo podría limitar la escala de UIPinchGestureRecognizer a un nivel mínimo y máximo? La propiedad de escala siguiente parece ser relativa a la última escala conocida (el delta del último estado) y no puedo encontrar la manera de establecer un límite para el tamaño / altura del objeto que se está acercando.

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

}


¿Puedes usar una vista de desplazamiento en su lugar? Entonces podría usar scrollView.minimumZoomScale y scrollView.maximumZoomScale


Aquí está la solución que descubrí después de usar la respuesta de Anomie como punto de partida.

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


El problema con la mayoría de las otras respuestas es que están tratando de lidiar con la escala como un valor lineal, cuando de hecho no es lineal debido a la forma en que UIPinchGestureRecognizer calcula su propiedad de escala en función de la distancia de contacto. Cuando esto no se toma en cuenta, el usuario debe usar más o menos distancia de pellizco para ''deshacer'' la escala aplicada por un gesto de pellizco anterior.

Considere: supongamos que transform.scale = 1.0 y coloco mis dedos a 6 cm de distancia en la pantalla, luego pellizco hacia dentro a 3 cm de distancia - el gestureRecognizer.scale resultante es 0.5 , y 0.5-1.0 es -0.5 , entonces transform.scale se convertirá en 1.0+(-0.5) = 0.5 . Ahora, levanto mis dedos, los coloco de nuevo a 3 cm de separación y los pellizco hacia afuera a 6 cm. El gestureRecognizer.scale resultante será 2.0 y 2.0-1.0 es 1.0 , por lo que transform.scale se convertirá en 0.5+1.0 = 1.5 . No es lo que quería que sucediera.

La solución es calcular la escala de pinzamiento delta como una proporción de su valor anterior. Coloco mis dedos a 6 cm de distancia, y pellizco hacia adentro a 3 cm, así gestureRecognizer.scale es 0.5 . 0.5/1.0 es 0.5 , entonces mi nueva transform.scale es 1.0*0.5 = 0.5 . A continuación, coloco mis dedos 3cm de separación, y pellizco hacia afuera a 6cm. gestureRecognizer.scale es 2.0 , y 2.0/1.0 es 2.0 , entonces mi nueva transform.scale es 0.5*2.0 = 1.0 , que es exactamente lo que quería que sucediera.

Aquí está en el código:

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


Gracias, muy útil fragmento de código por encima de la fijación a una escala mínima y máxima.

Lo encontré cuando cambié la vista primero usando:

CGAffineTransformScale(gestureRecognizer.view.transform, -1.0, 1.0);

causaría un parpadeo al escalar la vista.

Déjame saber lo que piensas, pero la solución para mí fue actualizar el ejemplo de código anterior, y si la vista se ha volteado (indicador establecido a través de la propiedad), entonces invierte el valor de la escala:

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


No hay forma de limitar la escala en un UIPinchGestureRecognizer . Para limitar la altura en tu código, deberías poder hacer algo como esto:

CGFloat scale = 1.0 - (lastScale - pinchscale); CGRect bounds = [(UIPinchGestureRecognizer*)sender view].bounds; scale = MIN(scale, maximumHeight / CGRectGetHeight(bounds)); scale = MAX(scale, minimumHeight / CGRectGetHeight(bounds));

Para limitar el ancho, cambie ''Altura'' a ''Ancho'' en las últimas dos líneas.


Otros enfoques mencionados aquí no funcionaron para mí, pero tomando un par de cosas de las respuestas anteriores y (en mi opinión) simplificar las cosas, tengo que trabajar para mí. effectiveScale es un ivar establecido en 1.0 en 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; } }


Tomé algo de información extraída de las respuestas de Paul Solt y Anoime, y agregué eso a una categoría existente que he hecho para UIViewController para permitir que cualquier UIView sea arrastrable, para hacerlo ahora pinchable usando gestos y transformaciones.

Nota: esto ensucia la propiedad de etiqueta de la vista que está haciendo arrastrable / pinchable. Entonces, si necesita la etiqueta para otra cosa, puede considerar colocar ese valor en el NSMutableDictionary utilizado por esta técnica. Está disponible como [self dictForView: theView]

Implementando en su proyecto:

Puede hacer que cualquier subvista dentro de la "vista" de los controladores de vista sea arrastrable o pinchable (o ambas) coloque una sola línea de código en su viewDidLoad (por ejemplo :)

[self makeView:mySubView draggable:YES pinchable:YES minPinchScale:0.75 maxPinchScale:1.0];

desactívelo en viewDidUnload (libera guestures y diccionario):

[self makeView:mySubView draggable:NO pinchable:NO minPinchScale:1.0 maxPinchScale:1.0];

Archivo DragAndPinchScale.h

#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

Archivo DragAndPinchScale.m

#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


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