2009-10-27 7 views
44

En caso de error en el inicio de sesión, prefiero evitar mostrar una alerta, es demasiado fugaz. Mostrar la alerta y luego mostrar el texto en algún lugar de la pantalla de inicio de sesión parece una duplicación.Agite el efecto visual en el iPhone (NO agite el dispositivo)

Así que me gustaría que agite gráficamente mi vista de inicio de sesión cuando el usuario introduce una ID de usuario y una contraseña incorrectas, como lo hace la pantalla de inicio de sesión de Mac.

¿Alguien sabe si hay una manera de lograr esto, o tiene alguna sugerencia para otro efecto que podría usar?

+0

5 años después ... http://stackoverflow.com/questions/24356051 – Fattie

Respuesta

97

Creo que esta es una solución más eficiente:

Swift:

let anim = CAKeyframeAnimation(keyPath:"transform") 
anim.values = [ 
    NSValue(CATransform3D:CATransform3DMakeTranslation(-5, 0, 0)), 
    NSValue(CATransform3D:CATransform3DMakeTranslation(5, 0, 0)) 
] 
anim.autoreverses = true 
anim.repeatCount = 2 
anim.duration = 7/100 

viewToShake.layer.addAnimation(anim, forKey:nil) 

Obj-C: Se crea

CAKeyframeAnimation * anim = [ CAKeyframeAnimation animationWithKeyPath:@"transform" ] ; 
anim.values = @[ 
    [ NSValue valueWithCATransform3D:CATransform3DMakeTranslation(-5.0f, 0.0f, 0.0f) ], 
    [ NSValue valueWithCATransform3D:CATransform3DMakeTranslation(5.0f, 0.0f, 0.0f) ] 
] ; 
anim.autoreverses = YES ; 
anim.repeatCount = 2.0f ; 
anim.duration = 0.07f ; 

[ viewToShake.layer addAnimation:anim forKey:nil ] ; 

Sólo un objeto de animación y todas sus realizaron en el nivel de CoreAnimation.

+0

Perfecto PERO para 2014, ver ... http://stackoverflow.com/questions/24356051 !! – Fattie

+1

Ver también Swift: http://stackoverflow.com/a/27988876/1438339 –

6

Simplemente cambiando la coordenada X de la propiedad central de su vista podría hacer el truco. Si no has hecho ninguna animación básica antes es bastante directo.

Primero, inicie una animación a la derecha, luego escuche para que termine, y luego muévala hacia la izquierda, y así sucesivamente. Disminuir el tiempo para que "se sienta bien" puede llevar un tiempo.

- (void)animationFinishCallback:(NSString *)animationID finished:(BOOL)finished context:(void *)context 
{ 
    if ([animationID isEqualToString:@"MoveRight"]) { 
    [UIView beginAnimations:@"MoveLeft" context:NULL]; 
    [UIView setAnimationDuration:1.0]; 
    [UIView setAnimationDelay: UIViewAnimationCurveEaseIn]; 
    [UIView setAnimationDelegate:self]; 
    [UIView setAnimationDidStopSelector:@selector(animationFinishCallback:finished:context:)]; 

    myView.center = CGRectMake(newX, newY); 
    [UIView commitAnimations]; 
    } 
} 
+4

También podría crear una animación y aplicarla al 'CALayer' de la vista. Esto le permitiría usar, por ejemplo, un 'CAKeyframeAnimation' que le permitiría ajustar la animación. Para una animación como esta, probablemente necesites ese nivel de flexibilidad. – Alex

+0

ver mi respuesta a continuación: utiliza una sola animación de CoreAnimation que se elimina automáticamente cuando finaliza la animación ... no hay necesidad de devoluciones de llamada o bloques animados múltiples – nielsbot

40

que habían visto algo de animación bamboleo y lo cambió a agitar una vista t píxeles en posición vertical y downleft:

- (void)earthquake:(UIView*)itemView 
{ 
    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:5]; 
    [UIView setAnimationDuration:0.07]; 
    [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; 
    } 
} 
59

Usando iOS 4+ bloque basada en animaciones UIKit (y vagamente basado en la respuesta de jayccrown):

- (void)shakeView:(UIView *)viewToShake 
{ 
    CGFloat t = 2.0; 
    CGAffineTransform translateRight = CGAffineTransformTranslate(CGAffineTransformIdentity, t, 0.0); 
    CGAffineTransform translateLeft = CGAffineTransformTranslate(CGAffineTransformIdentity, -t, 0.0); 

    viewToShake.transform = translateLeft; 

    [UIView animateWithDuration:0.07 delay:0.0 options:UIViewAnimationOptionAutoreverse|UIViewAnimationOptionRepeat animations:^{ 
     [UIView setAnimationRepeatCount:2.0]; 
     viewToShake.transform = translateRight; 
    } completion:^(BOOL finished) { 
     if (finished) { 
      [UIView animateWithDuration:0.05 delay:0.0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{ 
       viewToShake.transform = CGAffineTransformIdentity; 
      } completion:NULL]; 
     } 
    }]; 
} 
+4

Esta debería ser la respuesta más importante a la pregunta, ya que todas las respuestas anteriores no funciona correctamente (sin modificaciones u omisión del parámetro de visualización) con ARC activado. Usa bloques! –

+0

yup, esta es la mejor respuesta, es posible que no desee verificar 'finished', de lo contrario, puede terminar con una transformación impar – slf

+2

Puede hacer esto con solo una animación de autoreversión, sin necesidad de bloques de finalización. (Publiqué el código a continuación) – nielsbot

4

Este fragmento de categoría UIView funcionó para mí. Está utilizando 3 CABasingAnimations aplicadas a la capa de la vista.

#import <UIKit/UIKit.h> 
#import <QuartzCore/QuartzCore.h> 

#define Y_OFFSET 2.0f 
#define X_OFFSET 2.0f 
#define ANGLE_OFFSET (M_PI_4*0.1f) 

@interface UIView (shakeAnimation) 

-(BOOL)isShakeAnimationRunning; 
-(void)startShakeAnimation; 
-(void)stopShakeAnimation; 

@end 



@implementation UIView (shakeAnimation) 

-(BOOL)isShakeAnimationRunning{ 
    return [self.layer animationForKey:@"shake_rotation"] != nil; 
} 

-(void)startShakeAnimation{ 
    CFTimeInterval offset=(double)arc4random()/(double)RAND_MAX; 
    self.transform=CGAffineTransformRotate(self.transform, -ANGLE_OFFSET*0.5); 
    self.transform=CGAffineTransformTranslate(self.transform, -X_OFFSET*0.5f, -Y_OFFSET*0.5f); 

    CABasicAnimation *tAnim=[CABasicAnimation animationWithKeyPath:@"position.x"]; 
    tAnim.repeatCount=HUGE_VALF; 
    tAnim.byValue=[NSNumber numberWithFloat:X_OFFSET]; 
    tAnim.duration=0.07f; 
    tAnim.autoreverses=YES; 
    tAnim.timeOffset=offset; 
    [self.layer addAnimation:tAnim forKey:@"shake_translation_x"]; 

    CABasicAnimation *tyAnim=[CABasicAnimation animationWithKeyPath:@"position.y"]; 
    tyAnim.repeatCount=HUGE_VALF; 
    tyAnim.byValue=[NSNumber numberWithFloat:Y_OFFSET]; 
    tyAnim.duration=0.06f; 
    tyAnim.autoreverses=YES; 
    tyAnim.timeOffset=offset; 
    [self.layer addAnimation:tyAnim forKey:@"shake_translation_y"]; 

    CABasicAnimation *rAnim=[CABasicAnimation animationWithKeyPath:@"transform.rotation"]; 
    rAnim.repeatCount=HUGE_VALF; 
    rAnim.byValue=[NSNumber numberWithFloat:ANGLE_OFFSET]; 
    rAnim.duration=0.15f; 
    rAnim.autoreverses=YES; 
    rAnim.timeOffset=offset; 
    [self.layer addAnimation:rAnim forKey:@"shake_rotation"]; 
} 
-(void)stopShakeAnimation{ 
    [self.layer removeAnimationForKey:@"shake_translation_x"]; 
    [self.layer removeAnimationForKey:@"shake_translation_y"]; 
    [self.layer removeAnimationForKey:@"shake_rotation"]; 
    [UIView animateWithDuration:0.2f animations:^{ 
     self.transform=CGAffineTransformRotate(self.transform, ANGLE_OFFSET*0.5); 
     self.transform=CGAffineTransformTranslate(self.transform, X_OFFSET*0.5, Y_OFFSET*0.5f); 
    }]; 
} 

@end 

espero que alguien helpes :)

1

Sé que la pregunta ya está contestada, pero como ya he implementado algo así anteriormente, creo que no hace daño para agregarlo:

CAKeyframeAnimation *shakeAnimation = [CAKeyframeAnimation animationWithKeyPath:@"transform.rotation.z"]; 
NSArray *transformValues = [NSArray arrayWithObjects: 
         [NSNumber numberWithFloat:((M_PI)/64)], 
         [NSNumber numberWithFloat:(-((M_PI)/64))], 
         [NSNumber numberWithFloat:((M_PI)/64)], 
         [NSNumber numberWithFloat:(-((M_PI)/64))], 
         [NSNumber numberWithFloat:((M_PI)/64)], 
         [NSNumber numberWithFloat:(-((M_PI)/64))], 
         [NSNumber numberWithFloat:0],         
         nil]; 

[shakeAnimation setValues:transformValues]; 

NSArray *times = [NSArray arrayWithObjects: 
        [NSNumber numberWithFloat:0.14f], 
        [NSNumber numberWithFloat:0.28f], 
        [NSNumber numberWithFloat:0.42f], 
        [NSNumber numberWithFloat:0.57f], 
        [NSNumber numberWithFloat:0.71f], 
        [NSNumber numberWithFloat:0.85f], 
        [NSNumber numberWithFloat:1.0f], 
        nil]; 

[shakeAnimation setKeyTimes:times]; 

shakeAnimation.fillMode = kCAFillModeForwards; 
shakeAnimation.removedOnCompletion = NO; 
shakeAnimation.duration = 0.6f; 

[self.viewToShake.layer addAnimation:shakeAnimation forKey:@"anim"]; 

también, ya que desea que la agitación para indicar que el usuario no pudo iniciar sesión, también puede considerar la adición de esta animación que tiñe de color rojo la pantalla mientras la pantalla tiembla:

//Put this in the header (.h) 
@property (nonatomic, strong) UIView *redView; 

//Put this in the implementation (.m) 
@synthesize redView; 

//Put this in viewDidLoad 
self.redView = [[UIView alloc] initWithFrame:self.view.frame]; 
self.redView.layer.opacity = 0.0f; 
self.redView.layer.backgroundColor = [[UIColor redColor] CGColor]; 

//Put this wherever you check if the login failed 
CAKeyframeAnimation *redTint = [CAKeyframeAnimation animationWithKeyPath:@"opacity"]; 
NSArray *transformValues = [NSArray arrayWithObjects: 
          [NSNumber numberWithFloat:0.2f], 
          [NSNumber numberWithFloat:0.0f],         
          nil]; 

[redTint setValues:transformValues]; 

NSArray *times = [NSArray arrayWithObjects: 
        [NSNumber numberWithFloat:0.5f], 
        [NSNumber numberWithFloat:1.0f], 
        nil]; 

[redTint setKeyTimes:times]; 

redTint.fillMode = kCAFillModeForwards; 
redTint.removedOnCompletion = NO; 
redTint.duration = 0.6f; 

[self.redView.layer addAnimation:shakeAnimation forKey:@"anim"]; 

Espero que esto ayude!

0

Usando Auto Layout, me he adaptado respuesta Chris Miles, pero NSLayoutConstraints animados como esto:

NSLayoutConstraint *left = ... 
NSLayoutConstraint *right = ... 

[UIView animateWithDuration:0.08 delay:0.0 options:UIViewAnimationOptionAutoreverse|UIViewAnimationOptionRepeat animations:^{ 
    [UIView setAnimationRepeatCount:3]; 
    left.constant = 15.0; 
    right.constant = 25.0; 
    [self.view layoutIfNeeded]; 
} completion:^(BOOL finished) { 
    if (finished) { 
     [UIView animateWithDuration:0.08 animations:^{ 
      left.constant = 20.0; 
      right.constant = 20.0; 
      [self.view layoutIfNeeded]; 
     } completion:NULL]; 
    } 
}]; 
2

En iOS 7.0 o posterior, animación de fotogramas clave UIKit está disponible.

[UIView animateKeyframesWithDuration:0.5 delay:0.0 options:0 animations:^{ 
    [UIView setAnimationCurve:UIViewAnimationCurveLinear]; 

    NSInteger repeatCount = 8; 
    NSTimeInterval duration = 1.0/(NSTimeInterval)repeatCount; 

    for (NSInteger i = 0; i < repeatCount; i++) { 
     [UIView addKeyframeWithRelativeStartTime:i * duration relativeDuration:duration animations:^{ 
      CGFloat dx = 5.0; 
      if (i == repeatCount - 1) { 
       viewToShake.transform = CGAffineTransformIdentity; 
      } else if (i % 2) { 
       viewToShake.transform = CGAffineTransformTranslate(CGAffineTransformIdentity, -dx, 0.0); 
      } else { 
       viewToShake.transform = CGAffineTransformTranslate(CGAffineTransformIdentity, +dx, 0.0); 
      } 
     }]; 
    } 
} completion:completion]; 
0

Una solución que utilicé para las restricciones que establecí en mi guión gráfico. Sin usar animateWithDuration.

@IBOutlet var balloonHorizontalConstraint: NSLayoutConstraint! 

NSTimer.scheduledTimerWithTimeInterval(0.04, target: self, selector: "animateBalloon", userInfo: nil, repeats: true) 

func animateBalloon() { 
    switch balloonHorizontalConstraint.constant { 
    case -46: 
     balloonHorizontalConstraint.constant = -50 
    default: 
     balloonHorizontalConstraint.constant = -46 
    } 
} 

En mi caso la animación simplemente siguió adelante, pero yo pop mi viewcontroller después de una duración de unos pocos segundos, esto detiene mi aswell temporizador.

Cuestiones relacionadas