cómo crear el efecto de icono de wobbling de iphone?

Quiero tambalear una imagen hacia adelante y hacia atrás en mi aplicación de forma similar a cómo los icons del iPhone se tambalean cuando presionas hacia abajo. ¿Cuál es la mejor manera de hacer eso?

Esta es mi primera incursión en animaciones que no usa un GIF animado. Creo que la idea es girar ligeramente la imagen hacia adelante y hacia atrás para crear el efecto de tambaleo. He analizado el uso de CABasicAnimation y CAKeyframeAnimation. CABasicAnimation crea un jitter cada vez que se repite porque salta a la posición from y no se interpola. CAKeyframeAnimation parece ser la solución, excepto que no puedo hacer que funcione. Debo estar perdiendo algo. Aquí está mi código usando CAKeyframeAnimation (que no funciona):

NSString *keypath = @"wobbleImage"; CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:keypath]; animation.duration = 1.0f; animation.delegate = self; animation.repeatCount = 5; CGFloat wobbleAngle = 0.0872664626f; NSValue *initial = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(0.0f, 0.0f, 0.0f, 1.0f)]; NSValue *middle = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(wobbleAngle, 0.0f, 0.0f, 1.0f)]; NSValue *final = [NSValue valueWithCATransform3D:CATransform3DMakeRotation(-wobbleAngle, 0.0f, 0.0f, 1.0f)]; animation.values = [NSArray arrayWithObjects:initial, middle, final, nil]; [imageView.layer addAnimation:animation forKey:keypath]; 

O podría haber una solución totalmente más simple que me estoy perdiendo. Aprecie cualquier puntero. ¡Gracias!

Manera simple de hacerlo:

 #define RADIANS(degrees) (((degrees) * M_PI) / 180.0) CGAffineTransform leftWobble = CGAffineTransformRotate(CGAffineTransformIdentity, RADIANS(-5.0)); CGAffineTransform rightWobble = CGAffineTransformRotate(CGAffineTransformIdentity, RADIANS(5.0)); itemView.transform = leftWobble; // starting point [UIView beginAnimations:@"wobble" context:itemView]; [UIView setAnimationRepeatAutoreverses:YES]; // important [UIView setAnimationRepeatCount:10]; [UIView setAnimationDuration:0.25]; [UIView setAnimationDelegate:self]; [UIView setAnimationDidStopSelector:@selector(wobbleEnded:finished:context:)]; itemView.transform = rightWobble; // end here & auto-reverse [UIView commitAnimations]; ... - (void) wobbleEnded:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context { if ([finished boolValue]) { UIView* item = (UIView *)context; item.transform = CGAffineTransformIdentity; } } 

Probablemente tenga que jugar con el tiempo y los angularjs, pero esto debería ayudarlo a comenzar.

EDITAR: edité la respuesta para agregar código para devolver el elemento a su estado original cuando lo haya hecho. Además, tenga en cuenta que puede usar el valor del contexto beginAnimations para transmitir cualquier cosa a los métodos de inicio / detención. En este caso, es el objeto wobbling en sí mismo, por lo que no tiene que depender de ivars específicos y el método se puede usar para cualquier objeto genérico basado en UIView (es decir, tags de texto, imágenes, etc.)

La respuesta de Ramin fue muy buena, pero desde OS4 se puede lograr el mismo efecto usando animateWithDuration en una función simple también.

(adaptó su ejemplo para futuros googlers)

 #define RADIANS(degrees) (((degrees) * M_PI) / 180.0) - (void)startWobble { itemView.transform = CGAffineTransformRotate(CGAffineTransformIdentity, RADIANS(-5)); [UIView animateWithDuration:0.25 delay:0.0 options:(UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionRepeat | UIViewAnimationOptionAutoreverse) animations:^ { itemView.transform = CGAffineTransformRotate(CGAffineTransformIdentity, RADIANS(5)); } completion:NULL ]; } - (void)stopWobble { [UIView animateWithDuration:0.25 delay:0.0 options:(UIViewAnimationOptionAllowUserInteraction | UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveLinear) animations:^ { itemView.transform = CGAffineTransformIdentity; } completion:NULL ]; } 

Debe usar CAKeyframeAnimation para hacer una animación más suave.

 + (void) animationKeyFramed: (CALayer *) layer delegate: (id) object forKey: (NSString *) key { CAKeyframeAnimation *animation; animation = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation.z"]; animation.duration = 0.4; animation.cumulative = YES; animation.repeatCount = 2; animation.values = [NSArray arrayWithObjects: [NSNumber numberWithFloat: 0.0], [NSNumber numberWithFloat: RADIANS(-9.0)], [NSNumber numberWithFloat: 0.0], [NSNumber numberWithFloat: RADIANS(9.0)], [NSNumber numberWithFloat: 0.0], nil]; animation.fillMode = kCAFillModeForwards; animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; animation.removedOnCompletion = NO; animation.delegate = object; [layer addAnimation:animation forKey:key]; } 

He escrito una aplicación de muestra que intenta replicar el movimiento del icono y la swing de la pantalla de inicio: iPhone Sample Code: Tiles

La forma más fácil que conozco es usar Core Animation. Básicamente, creas un bloque de animación principal, luego haces una transformación de rotación y configuras y repites el conteo. Core Animation se encarga de todo lo que se necesita para hacer este efecto de tambaleo.

Para comenzar un bloque de Animación Core, solo hazlo:

 [UIView beginAnimations:@"any string as animationID" context:self]; [UIView setAnimationRepeatCount:10]; // rotate [UIView commitAnimations]; 

no probado. Pero puede ser que también deba hacer:

 [UIView setAnimationBeginsFromCurrentState:YES]; 

AFAIK setAnimationRepeatCount tendrá el efecto de que la animación termine, se deshaga, se haga, se deshaga, se haga, se deshaga, se haga … tantas veces como se especifique. Por lo tanto, es posible que desee girar primero a la izquierda sin recuento de repeticiones, y a partir de este punto comenzar a tambalearse con conteo repetido. Cuando termine, es posible que desee volver a girar a la transformación de identidad (= no se aplican rotaciones ni escalas).

Puede encadenar animaciones configurando el delegado de animación con

 [UIView setAnimationDelegate:self] 

y entonces

 [UIView setAnimationDidStopSelector:@selector(myMethod:finished:context:)]; 

y tan pronto como la animación se detenga, se llamará a ese método. Consulte la documentación de la clase UIView para saber cómo implementar ese método al que se llamará cuando se detenga la animación. Básicamente, dentro de ese método realizaría el siguiente paso (es decir, rotando hacia atrás, o cualquier otra cosa), con un nuevo bloque de animación pero el mismo contexto y la ID de animación, y luego (si es necesario) especificará otro didStopSelector.

ACTUALIZAR:

Es posible que desee verificar:

 [UIView setAnimationRepeatAutoreverses:YES]; 

esto se tambaleará hacia adelante y hacia atrás automáticamente.

Puede crear un efecto de swing no interminable utilizando CAKeyframeAnimation , así:

 CGFloat degrees = 8.0; CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation.z"]; animation.duration = 0.6; animation.cumulative = YES; animation.repeatCount = 1; animation.values = @[@0.0, @RADIANS(-degrees) * 0.25, @0.0, @RADIANS(degrees) * 0.5, @0.0, @RADIANS(-degrees), @0.0, @RADIANS(degrees), @0.0, @RADIANS(-degrees) * 0.5, @0.0, @RADIANS(degrees) * 0.25, @0.0]; animation.fillMode = kCAFillModeForwards; animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; animation.removedOnCompletion = YES; [self.layer addAnimation:animation forKey:@"wobble"]; 

Para cualquiera que haya encontrado esta publicación más recientemente y le gustaría hacer lo mismo en Swift, aquí está mi traducción:

 func smoothJiggle() { let degrees: CGFloat = 5.0 let animation = CAKeyframeAnimation(keyPath: "transform.rotation.z") animation.duration = 0.6 animation.cumulative = true animation.repeatCount = Float.infinity animation.values = [0.0, degreesToRadians(-degrees) * 0.25, 0.0, degreesToRadians(degrees) * 0.5, 0.0, degreesToRadians(-degrees), 0.0, degreesToRadians(degrees), 0.0, degreesToRadians(-degrees) * 0.5, 0.0, degreesToRadians(degrees) * 0.25, 0.0] animation.fillMode = kCAFillModeForwards; animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) animation.removedOnCompletion = true layer.addAnimation(animation, forKey: "wobble") } func stopJiggling() { jiggling = false self.layer.removeAllAnimations() self.transform = CGAffineTransformIdentity self.layer.anchorPoint = CGPointMake(0.5, 0.5) } 

Tarde a la fiesta. Normalmente uso spring con amortiguación (iOS7 y más reciente). En rápido parece:

  sender.transform = CGAffineTransformMakeScale(1.2, 1.2) UIView.animateWithDuration(0.30, delay: 0.0, usingSpringWithDamping: 0.3, initialSpringVelocity: 0.3, options: UIViewAnimationOptions.CurveEaseInOut, animations: { () -> Void in sender.transform = CGAffineTransformMakeScale(1, 1) }) { (Bool) -> Void in //Do stuff when animation is finished } 

Puede modificar el efecto ajustando initialSpringVelocity y SpringWithDamping

Basado en la respuesta de EsbenB, pero actualizado para Swift 3 y para la rotación:

  sender.transform = CGAffineTransform(rotationAngle: 12.0 * .pi / 180.0) UIView.animate(withDuration: 0.60, delay: 0.0, usingSpringWithDamping: 0.3, initialSpringVelocity: 0.3, options: .curveEaseInOut, animations: { () -> Void in sender.transform = CGAffineTransform(rotationAngle: 0.0) }, completion: nil) 

Bueno, el código proporcionado por Ramin funciona bien. Pero si usa la aplicación tabbar y pasa al elemento de la pestaña siguiente, vuelva al elemento de la pestaña anterior, verá que su vista se ha movido a la izquierda, todas las veces. Así que la mejor práctica es que utiliza el método ViewWillAppear como.

 - (void)viewWillAppear:(BOOL)animated { UIView* item = self.view; item.transform = CGAffineTransformIdentity; } 

de modo que cada vez que se carga la vista, encontrará la animación en el lugar correcto. Y también use este método.

 [UIView setAnimationDidStopSelector:@selector(myMethod:finished:context:)]; 

Swift 3

 func startWiggling() { deleteButton.isHidden = false guard contentView.layer.animation(forKey: "wiggle") == nil else { return } guard contentView.layer.animation(forKey: "bounce") == nil else { return } let angle = 0.04 let wiggle = CAKeyframeAnimation(keyPath: "transform.rotation.z") wiggle.values = [-angle, angle] wiggle.autoreverses = true wiggle.duration = randomInterval(0.1, variance: 0.025) wiggle.repeatCount = Float.infinity contentView.layer.add(wiggle, forKey: "wiggle") let bounce = CAKeyframeAnimation(keyPath: "transform.translation.y") bounce.values = [4.0, 0.0] bounce.autoreverses = true bounce.duration = randomInterval(0.12, variance: 0.025) bounce.repeatCount = Float.infinity contentView.layer.add(bounce, forKey: "bounce") } func stopWiggling() { deleteButton.isHidden = true contentView.layer.removeAllAnimations() }