UIView ¿Infinita animation de rotation de 360 ​​grados?

Estoy intentando rotar un UIImageView 360 grados, y he mirado varios tutoriales en línea. No pude conseguir que ninguno de ellos funcionara, sin que la UIView detuviera o saltara a una nueva position.

  • ¿Cómo puedo conseguir esto?

Lo último que he intentado es:

 [UIView animateWithDuration:1.0 delay:0.0 options:0 animations:^{ imageToMove.transform = CGAffineTransformMakeRotation(M_PI); } completion:^(BOOL finished){ NSLog(@"Done!"); }]; 

Pero si uso 2 * pi, no se mueve en absoluto (ya que es la misma position). Si trato de hacer solo pi (180 grados), funciona, pero si vuelvo a llamar al método, gira hacia atrás.

EDITAR :

 [UIView animateWithDuration:1.0 delay:0.0 options:0 animations:^{ [UIView setAnimationRepeatCount:HUGE_VALF]; [UIView setAnimationBeginsFromCurrentState:YES]; imageToMove.transform = CGAffineTransformMakeRotation(M_PI); } completion:^(BOOL finished){ NSLog(@"Done!"); }]; 

tampoco funciona Va a 180 grados, se detiene por una fracción de segundo, luego se restablece de nuevo a 0 grados antes de que comience de nuevo.

Encontré un método (lo modifiqué un poco) que funcionó perfectamente para mí: iPhone UIImageView rotation

 #import <QuartzCore/QuartzCore.h> - (void) runSpinAnimationOnView:(UIView*)view duration:(CGFloat)duration rotations:(CGFloat)rotations repeat:(float)repeat { CABasicAnimation* rotationAnimation; rotationAnimation = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"]; rotationAnimation.toValue = [NSNumber numberWithFloat: M_PI * 2.0 /* full rotation*/ * rotations * duration ]; rotationAnimation.duration = duration; rotationAnimation.cumulative = YES; rotationAnimation.repeatCount = repeat ? HUGE_VALF : 0; [view.layer addAnimation:rotationAnimation forKey:@"rotationAnimation"]; } 

Felicitaciones a Richard J. Ross III por la idea, pero descubrí que su código no era exactamente lo que necesitaba. El valor pnetworkingeterminado para las options , creo, es darte UIViewAnimationOptionCurveEaseInOut , que no se ve bien en una animation continua. Además, agregué un cheque para que pudiera detener mi animation en un cuarto de giro si necesitaba (no infinito , pero de duración indefinida ), e hizo que la aceleración boosta durante los primeros 90 grados, y que se desacelerara durante los últimos 90 grados (después de que se haya solicitado una parada):

 // an ivar for your class: BOOL animating; - (void)spinWithOptions:(UIViewAnimationOptions)options { // this spin completes 360 degrees every 2 seconds [UIView animateWithDuration:0.5 delay:0 options:options animations:^{ self.imageToMove.transform = CGAffineTransformRotate(imageToMove.transform, M_PI / 2); } completion:^(BOOL finished) { if (finished) { if (animating) { // if flag still set, keep spinning with constant speed [self spinWithOptions: UIViewAnimationOptionCurveLinear]; } else if (options != UIViewAnimationOptionCurveEaseOut) { // one last spin, with deceleration [self spinWithOptions: UIViewAnimationOptionCurveEaseOut]; } } }]; } - (void)startSpin { if (!animating) { animating = YES; [self spinWithOptions: UIViewAnimationOptionCurveEaseIn]; } } - (void)stopSpin { // set the flag to stop spinning after one last 90 degree increment animating = NO; } 

Actualizar

startSpin la capacidad de manejar requestes para comenzar a girar nuevamente ( startSpin ), mientras que la vuelta anterior se está terminando (completando). Muestra el proyecto aquí en Github .

La respuesta de Nate anterior es ideal para detener y comenzar la animation y proporciona un mejor control. Estaba intrigado por qué el tuyo no funcionó y el suyo. Quería compartir mis hallazgos aquí y una versión más simple del código que animaría una UIView continuamente sin estancamiento.

Este es el código que usé,

 - (void)rotateImageView { [UIView animateWithDuration:1 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{ [self.imageView setTransform:CGAffineTransformRotate(self.imageView.transform, M_PI_2)]; }completion:^(BOOL finished){ if (finished) { [self rotateImageView]; } }]; } 

Utilicé 'CGAffineTransformRotate' en lugar de 'CGAffineTransformMakeRotation' porque el primero devuelve el resultado que se guarda a medida que avanza la animation. Esto evitará saltar o restablecer la vista durante la animation.

Otra cosa es no usar 'UIViewAnimationOptionRepeat' porque al final de la animation antes de que comience a repetirse, restablece la transformación que hace que la vista vuelva a su position original. En lugar de una repetición, recursa para que la transformación nunca se restablezca al valor original porque el bloque de animation prácticamente nunca termina.

Y lo último es que debe transformar la vista en pasos de 90 grados (M_PI / 2) en lugar de 360 ​​o 180 grados (2 * M_PI o M_PI). Porque la transformación ocurre como una multiplicación matricial de valores de seno y coseno.

 t' = [ cos(angle) sin(angle) -sin(angle) cos(angle) 0 0 ] * t 

Entonces, diga si usa una transformación de 180 grados, el coseno de 180 produce -1 haciendo que la vista se transforme en la dirección opuesta cada vez (Nota: la respuesta de Nate también tendrá este problema si cambia el valor radian de la transformación a M_PI). Una transformación de 360 ​​grados es simplemente pedirle a la vista que permanezca donde estaba, por lo tanto, no ve ninguna rotation en absoluto.

En Swift, puede usar el siguiente código para la rotation infinita:

 let kRotationAnimationKey = "com.myapplication.rotationanimationkey" // Any key func rotateView(view: UIView, duration: Double = 1) { if view.layer.animationForKey(kRotationAnimationKey) == nil { let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation") rotationAnimation.fromValue = 0.0 rotationAnimation.toValue = Float(M_PI * 2.0) rotationAnimation.duration = duration rotationAnimation.repeatCount = Float.infinity view.layer.addAnimation(rotationAnimation, forKey: kRotationAnimationKey) } } 

Detener es como:

 func stopRotatingView(view: UIView) { if view.layer.animationForKey(kRotationAnimationKey) != nil { view.layer.removeAnimationForKey(kRotationAnimationKey) } } 

Si lo único que quiere hacer es rotar la image sin cesar, esto funciona bastante bien y es muy simple:

 NSTimeInterval duration = 10.0f; CGFloat angle = M_PI / 2.0f; CGAffineTransform rotateTransform = CGAffineTransformRotate(imageView.transform, angle); [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionRepeat| UIViewAnimationOptionCurveLinear animations:^{ imageView.transform = rotateTransform; } completion:nil]; 

En mi experiencia, esto funciona sin problemas, pero asegúrese de que su image sea capaz de rotar alnetworkingedor de su centro sin ningún desplazamiento, o que la animation de la image "saltará" una vez que se convierta en PI.

Para cambiar la dirección del giro, cambie el signo del angle ( angle *= -1 ).

Los comentarios de actualización de @AlexPretzlav me hicieron volver a ver esto, y me di count de que cuando escribía esto, la image que estaba rotando estaba reflejada a lo largo del eje vertical y horizontal, lo que significa que la image solo giraba 90 grados y luego se reiniciaba, aunque parecía seguía girando todo el path.

Por lo tanto, si su image es como la mía, esto funcionará muy bien, sin embargo, si la image no es simétrica, notará que el "snap" vuelve a la orientación original después de 90 grados.

Para rotar una image no simétrica, estará mejor con la respuesta aceptada.

Una de estas soluciones less elegantes, que se ve a continuación, realmente rotará la image, pero puede que se note un tartamudeo cuando se reinicia la animation:

 - (void)spin { NSTimeInterval duration = 0.5f; CGFloat angle = M_PI_2; CGAffineTransform rotateTransform = CGAffineTransformRotate(self.imageView.transform, angle); [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveLinear animations:^{ self.imageView.transform = rotateTransform; } completion:^(BOOL finished) { [self spin]; }]; } 

También podría hacerlo simplemente con bloques, como lo sugiere @ richard-j-ross-iii, pero recibirá una advertencia de bucle de retención ya que el bloque se está capturando:

 __block void(^spin)() = ^{ NSTimeInterval duration = 0.5f; CGFloat angle = M_PI_2; CGAffineTransform rotateTransform = CGAffineTransformRotate(self.imageView.transform, angle); [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveLinear animations:^{ self.imageView.transform = rotateTransform; } completion:^(BOOL finished) { spin(); }]; }; spin(); 

Mi contribución con una extensión Swift desde la solución comprobada:

 extension UIView{ func rotate() { let rotation : CABasicAnimation = CABasicAnimation(keyPath: "transform.rotation.z") rotation.toValue = NSNumber(double: M_PI * 2) rotation.duration = 1 rotation.cumulative = true rotation.repeatCount = FLT_MAX self.layer.addAnimation(rotation, forKey: "rotationAnimation") } } 

Use un cuarto de vuelta y aumente el giro gradualmente.

 void (^block)() = ^{ imageToMove.transform = CGAffineTransformRotate(imageToMove.transform, M_PI / 2); } void (^completion)(BOOL) = ^(BOOL finished){ [UIView animateWithDuration:1.0 delay:0.0 options:0 animations:block completion:completion]; } completion(YES); 

He encontrado un buen código en este repository ,

Aquí está el código de él, he hecho pequeños cambios de acuerdo con mi necesidad de velocidad 🙂

UIImageView + Rotate.h

 #import <Foundation/Foundation.h> @interface UIImageView (Rotate) - (void)rotate360WithDuration:(CGFloat)duration repeatCount:(float)repeatCount; - (void)pauseAnimations; - (void)resumeAnimations; - (void)stopAllAnimations; @end 

UIImageView + Rotate.m

 #import <QuartzCore/QuartzCore.h> #import "UIImageView+Rotate.h" @implementation UIImageView (Rotate) - (void)rotate360WithDuration:(CGFloat)duration repeatCount:(float)repeatCount { CABasicAnimation *fullRotation; fullRotation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"]; fullRotation.fromValue = [NSNumber numberWithFloat:0]; //fullRotation.toValue = [NSNumber numberWithFloat:(2*M_PI)]; fullRotation.toValue = [NSNumber numberWithFloat:-(2*M_PI)]; // added this minus sign as i want to rotate it to anticlockwise fullRotation.duration = duration; fullRotation.speed = 2.0f; // Changed rotation speed if (repeatCount == 0) fullRotation.repeatCount = MAXFLOAT; else fullRotation.repeatCount = repeatCount; [self.layer addAnimation:fullRotation forKey:@"360"]; } //Not using this methods :) - (void)stopAllAnimations { [self.layer removeAllAnimations]; }; - (void)pauseAnimations { [self pauseLayer:self.layer]; } - (void)resumeAnimations { [self resumeLayer:self.layer]; } - (void)pauseLayer:(CALayer *)layer { CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil]; layer.speed = 0.0; layer.timeOffset = pausedTime; } - (void)resumeLayer:(CALayer *)layer { CFTimeInterval pausedTime = [layer timeOffset]; layer.speed = 1.0; layer.timeOffset = 0.0; layer.beginTime = 0.0; CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime; layer.beginTime = timeSincePause; } @end 

Esto estaba funcionando para mí:

 [UIView animateWithDuration:1.0 animations:^ { self.imageView.transform = CGAffineTransformMakeRotation(M_PI); self.imageView.transform = CGAffineTransformMakeRotation(0); }]; 

Aquí está mi solución rápida como una extensión UIView. Podría considerarse como una simulación de un comportamiento UIActivityIndicator en cualquier UIImageView.

 import UIKit extension UIView { /** Starts rotating the view around Z axis. @param duration Duration of one full 360 degrees rotation. One second is default. @param repeatCount How many times the spin should be done. If not provided, the view will spin forever. @param clockwise Direction of the rotation. Default is clockwise (true). */ func startZRotation(duration duration: CFTimeInterval = 1, repeatCount: Float = Float.infinity, clockwise: Bool = true) { if self.layer.animationForKey("transform.rotation.z") != nil { return } let animation = CABasicAnimation(keyPath: "transform.rotation.z") let direction = clockwise ? 1.0 : -1.0 animation.toValue = NSNumber(double: M_PI * 2 * direction) animation.duration = duration animation.cumulative = true animation.repeatCount = repeatCount self.layer.addAnimation(animation, forKey:"transform.rotation.z") } /// Stop rotating the view around Z axis. func stopZRotation() { self.layer.removeAnimationForKey("transform.rotation.z") } } 

Una versión Swift3:

 extension UIView { func startRotate() { let rotation : CABasicAnimation = CABasicAnimation(keyPath: "transform.rotation.z") rotation.fromValue = 0 rotation.toValue = NSNumber(value: M_PI * 2) rotation.duration = 2 rotation.isCumulative = true rotation.repeatCount = FLT_MAX self.layer.add(rotation, forKey: "rotationAnimation") } func stopRotate() { self.layer.removeAnimation(forKey: "rotationAnimation") } } 

Y recuerde llamar a startRotate en viewWillAppear no en viewDidLoad .

También puede hacer el mismo tipo de animation usando UIView y bloques. Aquí hay un método de extensión de class que puede rotar la vista por cualquier ángulo.

 - (void)rotationWithDuration:(NSTimeInterval)duration angle:(CGFloat)angle options:(UIViewAnimationOptions)options { // Repeat a quarter rotation as many times as needed to complete the full rotation CGFloat sign = angle > 0 ? 1 : -1; __block NSUInteger numberRepeats = floorf(fabsf(angle) / M_PI_2); CGFloat quarterDuration = duration * M_PI_2 / fabs(angle); CGFloat lastRotation = angle - sign * numberRepeats * M_PI_2; CGFloat lastDuration = duration - quarterDuration * numberRepeats; __block UIViewAnimationOptions startOptions = UIViewAnimationOptionBeginFromCurrentState; UIViewAnimationOptions endOptions = UIViewAnimationOptionBeginFromCurrentState; if (options & UIViewAnimationOptionCurveEaseIn || options == UIViewAnimationOptionCurveEaseInOut) { startOptions |= UIViewAnimationOptionCurveEaseIn; } else { startOptions |= UIViewAnimationOptionCurveLinear; } if (options & UIViewAnimationOptionCurveEaseOut || options == UIViewAnimationOptionCurveEaseInOut) { endOptions |= UIViewAnimationOptionCurveEaseOut; } else { endOptions |= UIViewAnimationOptionCurveLinear; } void (^lastRotationBlock)(void) = ^ { [UIView animateWithDuration:lastDuration delay:0 options:endOptions animations:^{ self.transform = CGAffineTransformRotate(self.transform, lastRotation); } completion:^(BOOL finished) { NSLog(@"Animation completed"); } ]; }; if (numberRepeats) { __block void (^quarterSpinningBlock)(void) = ^{ [UIView animateWithDuration:quarterDuration delay:0 options:startOptions animations:^{ self.transform = CGAffineTransformRotate(self.transform, M_PI_2); numberRepeats--; } completion:^(BOOL finished) { if (numberRepeats > 0) { startOptions = UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveLinear; quarterSpinningBlock(); } else { lastRotationBlock(); }NSLog(@"Animation completed"); } ]; }; quarterSpinningBlock(); } else { lastRotationBlock(); } } 

Si alguien quería la solución de Nates, pero de manera rápida, aquí hay una traducción aproximada y rápida:

 class SomeClass: UIViewController { var animating : Bool = false @IBOutlet weak var activityIndicatorImage: UIImageView! func startSpinning() { if(!animating) { animating = true; spinWithOptions(UIViewAnimationOptions.CurveEaseIn); } } func stopSpinning() { animating = false } func spinWithOptions(options: UIViewAnimationOptions) { UIView.animateWithDuration(0.5, delay: 0.0, options: options, animations: { () -> Void in let val : CGFloat = CGFloat((M_PI / Double(2.0))); self.activityIndicatorImage.transform = CGAffineTransformRotate(self.activityIndicatorImage.transform,val) }) { (finished: Bool) -> Void in if(finished) { if(self.animating){ self.spinWithOptions(UIViewAnimationOptions.CurveLinear) } else if (options != UIViewAnimationOptions.CurveEaseOut) { self.spinWithOptions(UIViewAnimationOptions.CurveEaseOut) } } } } override func viewDidLoad() { startSpinning() } } 

Así es como rotar 360 en la dirección correcta.

 [UIView animateWithDuration:1.0f delay:0.0f options:UIViewAnimationOptionRepeat|UIViewAnimationOptionCurveLinear animations:^{ [imageIndView setTransform:CGAffineTransformRotate([imageIndView transform], M_PI-0.00001f)]; } completion:nil]; 

La respuesta de @ ram fue realmente útil. Aquí hay una versión Swift de la respuesta.

 private func rotateImageView() { UIView.animateWithDuration(1, delay: 0, options: UIViewAnimationOptions.CurveLinear, animations: { () -> Void in self.imageView.transform = CGAffineTransformRotate(self.imageView.transform, CGFloat(M_PI_2)) }) { (finished) -> Void in if finished { self.rotateImageView() } } } 

Crea la animacion

 - (CABasicAnimation *)spinAnimationWithDuration:(CGFloat)duration clockwise:(BOOL)clockwise repeat:(BOOL)repeats { CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"]; anim.toValue = clockwise ? @(M_PI * 2.0) : @(M_PI * -2.0); anim.duration = duration; anim.cumulative = YES; anim.repeatCount = repeats ? CGFLOAT_MAX : 0; return anim; } 

Agréguelo a una vista como esta

 CABasicAnimation *animation = [self spinAnimationWithDuration:1.0 clockwise:YES repeat:YES]; [self.spinningView.layer addAnimation:animation forKey:@"rotationAnimation"]; 

¿Cómo es diferente esta respuesta? Tendrás un código más limpio si la mayoría de tus funciones devuelve objects en lugar de solo manipular algunos objects aquí y allá.

para xamarin ios:

 public static void RotateAnimation (this UIView view, float duration=1, float rotations=1, float repeat=int.MaxValue) { var rotationAnimation = CABasicAnimation.FromKeyPath ("transform.rotation.z"); rotationAnimation.To = new NSNumber (Math.PI * 2.0 /* full rotation*/ * 1 * 1); rotationAnimation.Duration = 1; rotationAnimation.Cumulative = true; rotationAnimation.RepeatCount = int.MaxValue; rotationAnimation.RemovedOnCompletion = false; view.Layer.AddAnimation (rotationAnimation, "rotationAnimation"); } 

¡He desarrollado un marco de animation shiny que puede ahorrarle el tono del time! Usándolo, esta animation se puede crear fácilmente:

 private var endlessRotater: EndlessAnimator! override func viewDidAppear(animated: Bool) { super.viewDidAppear(animated) let rotationAnimation = AdditiveRotateAnimator(M_PI).to(targetView).duration(2.0).baseAnimation(.CurveLinear) endlessRotater = EndlessAnimator(rotationAnimation) endlessRotater.animate() } 

Para detener esta animation simplemente establezca nil a endlessRotater .

Si está interesado, eche un vistazo: https://github.com/hip4yes/Animatics

Swift:

 func runSpinAnimationOnView(view:UIView , duration:Float, rotations:Double, repeatt:Float ) ->() { let rotationAnimation=CABasicAnimation(); rotationAnimation.keyPath="transform.rotation.z" let toValue = M_PI * 2.0 * rotations ; // passing it a float let someInterval = CFTimeInterval(duration) rotationAnimation.toValue=toValue; rotationAnimation.duration=someInterval; rotationAnimation.cumulative=true; rotationAnimation.repeatCount=repeatt; view.layer.addAnimation(rotationAnimation, forKey: "rotationAnimation") } 

Swift 3:

  var rotationAnimation = CABasicAnimation() rotationAnimation = CABasicAnimation.init(keyPath: "transform.rotation.z") rotationAnimation.toValue = NSNumber(value: (M_PI * 2.0)) rotationAnimation.duration = 2.0 rotationAnimation.isCumulative = true rotationAnimation.repeatCount = 10.0 view.layer.add(rotationAnimation, forKey: "rotationAnimation") 
 let val = CGFloat(M_PI_2) UIView.animate(withDuration: 1, delay: 0, options: [.repeat, .curveLinear], animations: { self.viewToRotate.transform = self.viewToRotate.transform.rotated(by: val) }) 

Existen diferentes forms de realizar animation de 360 ​​grados con UIView.

Utilizando CABasicAnimation

 var rotationAnimation = CABasicAnimation() rotationAnimation = CABasicAnimation.init(keyPath: "transform.rotation.z") rotationAnimation.toValue = NSNumber(value: (Double.pi)) rotationAnimation.duration = 1.0 rotationAnimation.isCumulative = true rotationAnimation.repeatCount = 100.0 view.layer.add(rotationAnimation, forKey: "rotationAnimation") 

Aquí hay una funciones de extensión para UIView que maneja las operaciones de inicio y parada de rotation:

 extension UIView { func startRotation() { let rotation = CABasicAnimation(keyPath: "transform.rotation.z") rotation.fromValue = 0 rotation.toValue = NSNumber(value: Double.pi) rotation.duration = 1.0 rotation.isCumulative = true rotation.repeatCount = FLT_MAX self.layer.add(rotation, forKey: "rotationAnimation") } func stopRotation() { self.layer.removeAnimation(forKey: "rotationAnimation") } } 

Ahora usando, cierre de UIView.animation:

 UIView.animate(withDuration: 0.5, animations: { view.transform = CGAffineTransform(rotationAngle: (CGFloat(Double.pi)) }) { (isAnimationComplete) in // Animation completed } 

Swift 4 ,

 func rotateImage(image: UIImageView) { UIView.animate(withDuration: 1, animations: { image.transform = CGAffineTransform(rotationAngle: CGFloat.pi) image.transform = CGAffineTransform.identity }) { (completed) in self.rotateImage() } } 

Árbitro

Creo que deberías agregar una categoría UIVIew :

 #import <QuartzCore/QuartzCore.h> #import "UIView+Rotate.h" 

Implementación UIView (Girar)

  •  (void)remrotate360WithDuration:(CGFloat)duration repeatCount: (float)repeatCount { CABasicAnimation *fullRotation; fullRotation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"]; fullRotation.fromValue = [NSNumber numberWithFloat:0]; fullRotation.toValue = [NSNumber numberWithFloat:(2*M_PI)]; // fullRotation.toValue = [NSNumber numberWithFloat:-(2*M_PI)]; // added this minus sign as i want to rotate it to anticlockwise fullRotation.duration = duration; fullRotation.speed = 2.0f; // Changed rotation speed if (repeatCount == 0) fullRotation.repeatCount = MAXFLOAT; else fullRotation.repeatCount = repeatCount; [self.layer addAnimation:fullRotation forKey:@"360"]; } 

No usar estos methods 🙂

  •  (void)remstopAllAnimations { [self.layer removeAllAnimations]; }; 
  •  (void)rempauseAnimations { [self rempauseLayer:self.layer]; } 
  •  (void)remresumeAnimations { [self remresumeLayer:self.layer]; } 
  •  (void)rempauseLayer:(CALayer *)layer { CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil]; layer.speed = 0.0; layer.timeOffset = pausedTime; } 
  •  (void)remresumeLayer:(CALayer *)layer { CFTimeInterval pausedTime = [layer timeOffset]; layer.speed = 1.0; layer.timeOffset = 0.0; layer.beginTime = 0.0; CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime; layer.beginTime = timeSincePause; }