UIView shake animation

Estoy tratando de hacer un movimiento de UIView cuando se presiona un botón.

Estoy adaptando el código que encontré en http://www.cimgf.com/2008/02/27/core-animation-tutorial-window-shake-effect/ .

Sin embargo, al tratar de adaptar el siguiente código para sacudir un UIView, no funciona:

- (void)animate { const int numberOfShakes = 8; const float durationOfShake = 0.5f; const float vigourOfShake = 0.1f; CAKeyframeAnimation *shakeAnimation = [CAKeyframeAnimation animation]; CGRect frame = lockView.frame; CGMutablePathRef shakePath = CGPathCreateMutable(); CGPathMoveToPoint(shakePath, NULL, CGRectGetMinX(frame), CGRectGetMinY(frame)); for (int index = 0; index < numberOfShakes; ++index) { CGPathAddLineToPoint(shakePath, NULL, CGRectGetMinX(frame) - frame.size.width * vigourOfShake, CGRectGetMinY(frame)); CGPathAddLineToPoint(shakePath, NULL, CGRectGetMinX(frame) + frame.size.width * vigourOfShake, CGRectGetMinY(frame)); } CGPathCloseSubpath(shakePath); shakeAnimation.path = shakePath; shakeAnimation.duration = durationOfShake; [lockView.layer addAnimation:shakeAnimation forKey:@"frameOrigin"]; } 

    Escribí esa publicación. Es excesivo para una UIView, además los parámetros están orientados a una aplicación OSX. Haz esto en su lugar.

     CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position"]; [animation setDuration:0.05]; [animation setRepeatCount:8]; [animation setAutoreverses:YES]; [animation setFromValue:[NSValue valueWithCGPoint: CGPointMake([lockView center].x - 20.0f, [lockView center].y)]]; [animation setToValue:[NSValue valueWithCGPoint: CGPointMake([lockView center].x + 20.0f, [lockView center].y)]]; [[lockView layer] addAnimation:animation forKey:@"position"]; 

    Tendrá que jugar con la duración y los parámetros repeatCount, así como la distancia x desde el centro en los valores from y to, pero debe darle lo que necesita. Espero que eso ayude. Hazme saber si tienes alguna pregunta.


    Swift 3.0

     let midX = lockView.center.x let midY = lockView.center.y let animation = CABasicAnimation(keyPath: "position") animation.duration = 0.06 animation.repeatCount = 4 animation.autoreverses = true animation.fromValue = CGPoint(x: midX - 10, y: midY) animation.toValue = CGPoint(x: midX + 10, y: midY) layer.add(animation, forKey: "position") 

    Prefiero esta solución que tiene un buen comportamiento elástico, ideal para una animación de batido de contraseña incorrecta.

     view.transform = CGAffineTransformMakeTranslation(20, 0); [UIView animateWithDuration:0.4 delay:0.0 usingSpringWithDamping:0.2 initialSpringVelocity:1.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{ view.transform = CGAffineTransformIdentity; } completion:nil]; 

    Swift 3

     extension UIView { func shake() { self.transform = CGAffineTransform(translationX: 20, y: 0) UIView.animate(withDuration: 0.4, delay: 0, usingSpringWithDamping: 0.2, initialSpringVelocity: 1, options: .curveEaseInOut, animations: { self.transform = CGAffineTransform.identity }, completion: nil) } } 

    Aquí está mi versión atractiva y sencilla. Simula la sacudida que obtienes en Mac OS X cuando realizas un inicio de sesión incorrecto. Puede agregar esto como una categoría en UIView si lo desea.

     @implementation UIView (DUExtensions) - (void) shake { CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:@"transform.translation.x"]; animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; animation.duration = 0.6; animation.values = @[ @(-20), @(20), @(-20), @(20), @(-10), @(10), @(-5), @(5), @(0) ]; [self.layer addAnimation:animation forKey:@"shake"]; } @end 

    Los valores de animación son el desplazamiento x desde la posición actual de las vistas. Valores positivos que cambian la vista a la derecha y valores negativos a la izquierda. Al bajarlos sucesivamente, obtienes un batido que, naturalmente, pierde impulso. Puede ajustar estos números si lo desea.

    Aquí está la versión rápida como una extensión en caso de que alguien lo necesite

     extension UIImageView{ func vibrate(){ let animation = CABasicAnimation(keyPath: "position") animation.duration = 0.05 animation.repeatCount = 5 animation.autoreverses = true animation.fromValue = NSValue(CGPoint: CGPointMake(self.center.x - 2.0, self.center.y)) animation.toValue = NSValue(CGPoint: CGPointMake(self.center.x + 2.0, self.center.y)) self.layer.addAnimation(animation, forKey: "position") } } 

    Esto animará un pequeño UIImageView (alrededor de 15×15). Si necesita animar algo más grande, puede cambiar el factor de movimiento 2.0 a algo mayor.

    Basado en @bandejapaisa answer, extensión UIView para Swift 3

     extension UIView { func shake() { let animation = CAKeyframeAnimation(keyPath: "transform.translation.x") animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) animation.duration = 0.6 animation.values = [-20, 20, -20, 20, -10, 10, -5, 5, 0] layer.addAnimation(animation, forKey: "shake") } } 

    Puedes probar esta pieza de código:

    para llamar al código a continuación, use: [self earthquake:myObject];

     #pragma mark EarthQuake Methods - (void)earthquake:(UIView*)itemView { AudioServicesPlaySystemSound(kSystemSoundID_Vibrate); CGFloat t = 2.0; CGAffineTransform leftQuake = CGAffineTransformTranslate(CGAffineTransformIdentity, t, -t); CGAffineTransform rightQuake = CGAffineTransformTranslate(CGAffineTransformIdentity, -t, t); itemView.transform = leftQuake; // starting point [UIView beginAnimations:@"earthquake" context:itemView]; [UIView setAnimationRepeatAutoreverses:YES]; // important [UIView setAnimationRepeatCount:3]; [UIView setAnimationDuration:0.05]; [UIView setAnimationDelegate:self]; [UIView setAnimationDidStopSelector:@selector(earthquakeEnded:finished:context:)]; itemView.transform = rightQuake; // end here & auto-reverse [UIView commitAnimations]; } - (void)earthquakeEnded:(NSString *)animationID finished:(NSNumber *)finished context:(void *)context { if ([finished boolValue]) { UIView* item = (UIView *)context; item.transform = CGAffineTransformIdentity; } } 

    Puede llamar a este método en el evento UIButton click

     -(void)shakescreen { //Shake screen CGFloat t = 5.0; CGAffineTransform translateRight = CGAffineTransformTranslate(CGAffineTransformIdentity, t, t); CGAffineTransform translateLeft = CGAffineTransformTranslate(CGAffineTransformIdentity, -t, -t); self.view.transform = translateLeft; [UIView animateWithDuration:0.05 delay:0.0 options:UIViewAnimationOptionAutoreverse|UIViewAnimationOptionRepeat animations:^ { [UIView setAnimationRepeatCount:2.0]; self.view.transform = translateRight; } completion:^(BOOL finished) { if (finished) { [UIView animateWithDuration:0.05 delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^ { self.view.transform = CGAffineTransformIdentity; } completion:NULL]; } }]; } 

    Espero que esto te ayudará 🙂

    C # Xamarin.iOS versión de respuesta cómo crear la animación de UIView Shake en iOS está debajo

      CAKeyFrameAnimation keyframeAnimation = CAKeyFrameAnimation.GetFromKeyPath(new NSString("transform.translation.x")); keyframeAnimation.TimingFunction = CAMediaTimingFunction.FromName(CAMediaTimingFunction.EaseInEaseOut); keyframeAnimation.Duration = 0.6f; keyframeAnimation.Values = new NSObject[]{ new NSNumber(-20f), new NSNumber(20f), new NSNumber(-20f), new NSNumber(20f), new NSNumber(-10f), new NSNumber(10f), new NSNumber(-5f), new NSNumber(5f), new NSNumber(0f) }; shakyView.Layer.AddAnimation(keyframeAnimation, "shake"); 

    Aquí hay uno que usa una función de amortiguación para descomponer el batido:

     - (void)shake { CAKeyframeAnimation* animation = [CAKeyframeAnimation animationWithKeyPath:@"position"]; animation.duration = 0.5; animation.delegate = self; animation.fillMode = kCAFillModeForwards; animation.removedOnCompletion = YES; animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; NSMutableArray* values = [[NSMutableArray alloc] init]; int steps = 100; double position = 0; float e = 2.71; for (int t = 0; t < steps; t++) { position = 10 * pow(e, -0.022 * t) * sin(0.12 * t); NSValue* value = [NSValue valueWithCGPoint:CGPointMake([self center].x - position, [self center].y)]; DDLogInfo(@"Value: %@", value); [values addObject:value]; } animation.values = values; [[self layer] addAnimation:animation forKey:@"position"]; } 

    Implementación de Swift 3 basada en la respuesta @ Mihael-Isaev

     private enum Axis: StringLiteralType { case x = "x" case y = "y" } extension UIView { private func shake(on axis: Axis) { let animation = CAKeyframeAnimation(keyPath: "transform.translation.\(axis.rawValue)") animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) animation.duration = 0.6 animation.values = [-20, 20, -20, 20, -10, 10, -5, 5, 0] layer.add(animation, forKey: "shake") } func shakeOnXAxis() { self.shake(on: .x) } func shakeOnYAxis() { self.shake(on: .y) } } 

    Puedes probar el siguiente código:

     + (void)vibrateView:(UIView*)view { CABasicAnimation *shiverAnimationR; shiverAnimationR = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"]; shiverAnimationR.toValue = [NSNumber numberWithFloat:DEGREES_TO_RADIANS(1)]; //shiverAnimationR.toValue = [NSNumber numberWithFloat:DEGREES_TO_RADIANS(-10)]; shiverAnimationR.duration = 0.1; shiverAnimationR.repeatCount = 1000000.0; // Use A high Value shiverAnimationR.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]; [view.layer addAnimation: shiverAnimationR forKey:@"shiverAnimationR"]; CABasicAnimation * shiverAnimationL; shiverAnimationL = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"]; //shiverAnimationL 2.toValue = [NSNumber numberWithFloat:DEGREES_TO_RADIANS(10)]; shiverAnimationL.toValue = [NSNumber numberWithFloat:DEGREES_TO_RADIANS(-1)]; shiverAnimationL.duration = 0.1; shiverAnimationL.repeatCount = 1000000.0; shiverAnimationL.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn]; [view.layer addAnimation: shiverAnimationL forKey:@"shiverAnimationL"]; } 

    Desde el enlace

    Refactore el código @Matt Long e hice una categoría para UIView . Ahora es mucho más reutilizable y fácil de usar.

     @implementation UIView (Animation) - (void)shakeViewWithOffest:(CGFloat)offset { CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"position.x"]; [animation setDuration:0.05]; [animation setRepeatCount:6]; [animation setAutoreverses:YES]; [animation setFromValue:@([self center].x-offset)]; [animation setToValue:@([self center].x+offset)]; [self.layer addAnimation:animation forKey:@"position.x"]; } - (void)shake { [self shakeViewWithOffest:7.0f]; } @end 

    Aquí hay una versión que usa,

     + (void)animateKeyframesWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewKeyframeAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion 

    Introducido en iOS 7.

      const CGFloat xDelta = 16.0f; [UIView animateKeyframesWithDuration:0.50f delay:0.0f options:UIViewKeyframeAnimationOptionCalculationModeLinear animations:^{ [UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:(1.0/6.0) animations:^{ self.passwordTextField.transform = self.usernameTextField.transform = CGAffineTransformMakeTranslation(xDelta, 0.0); }]; [UIView addKeyframeWithRelativeStartTime:(1.0/6.0) relativeDuration:(1.0/6.0) animations:^{ self.passwordTextField.transform = self.usernameTextField.transform = CGAffineTransformMakeTranslation(-xDelta, 0.0); }]; [UIView addKeyframeWithRelativeStartTime:(1.0/3.0) relativeDuration:(1.0/3.0) animations:^{ self.passwordTextField.transform = self.usernameTextField.transform = CGAffineTransformMakeTranslation(xDelta/2.0, 0.0); }]; [UIView addKeyframeWithRelativeStartTime:(2.0/3.0) relativeDuration:(1.0/3.0) animations:^{ self.passwordTextField.transform = self.usernameTextField.transform = CGAffineTransformIdentity; }]; } completion:NULL];