web-dev-qa-db-fra.com

Secouez l'effet visuel sur l'iPhone (ne rien faire)

En cas d’échec de la connexion, je préférerais éviter d’afficher une alerte, c’est trop fugace. Afficher l'alerte, puis afficher le texte quelque part sur l'écran de connexion semble être une duplication.

Je souhaite donc que ma vue de connexion soit remuée de manière graphique lorsque l'utilisateur entre le mauvais identifiant et mot de passe, comme le fait l'écran de connexion Mac.

Quelqu'un sait s'il existe un moyen de résoudre ce problème ou si vous avez des suggestions pour un autre effet que je pourrais utiliser? 

44
Steven Fisher

Je pense que c'est une solution plus efficace:

Rapide:

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:

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

Un seul objet d'animation est créé et tout est exécuté au niveau CoreAnimation.

97
nielsbot

Utilisation des animations UIKit basées sur des blocs iOS 4+ (et vaguement basée sur la réponse 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];
        }
    }];
}
59
Chris Miles

J'avais vu une animation de vobulation et l'avais modifiée pour faire basculer une vue en pixels verticalement et verticalement:

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

Voici un tutoriel qui explique comment le faire dans Cocoa. Devrait être le même pour l'iPhone (ou au moins assez similaire).

http://www.cimgf.com/2008/02/27/core-animation-tutorial-window-shake-effect/

8
Justin Gallagher

Changer simplement la coordonnée X de la propriété center de votre vue peut faire l'affaire. Si vous n'avez pas fait d'animation de base avant, c'est assez simple.

Commencez par lancer une animation à droite, puis écoutez-la pour qu'elle se termine, puis revenez à gauche et ainsi de suite. Réduire le temps pour qu'il se "sent bien" peut prendre un certain temps.

- (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];
  }
}
6
slf

Cet extrait de la catégorie UIView a fonctionné pour moi. Il utilise 3 CABasingAnimations appliquées au calque de la vue.

#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

J'espère que ça aide quelqu'un :)

4
JakubKnejzlik

Dans iOS 7.0 ou version ultérieure, une animation par images clés 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];
2
ishkawa

Je sais que la question a déjà reçu une réponse, mais comme j’ai déjà implémenté quelque chose comme cela précédemment, j’ai le sentiment qu’il n’a aucun mal à l’ajouter:

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

De plus, comme vous voulez que l'agitation indique que l'utilisateur n'a pas réussi à se connecter, vous pouvez également envisager d'ajouter cette animation qui teinte l'écran en rouge lorsque l'écran est secoué:

//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"];

J'espère que cela t'aides!

1
pasawaya

catégorie de shake très facile pour UIVoew

https://github.com/jonasschnelli/UIView-I7ShakeAnimation

1
Jonas Schnelli

Une solution que j'ai utilisée pour les contraintes que j'ai définies dans mon storyboard. Sans utiliser 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
    }
}

Dans mon cas, l'animation continuait, mais j'ouvre mon contrôleur de vue après une durée de quelques secondes, cela arrête également mon chronomètre. 

0
Alserda

À l'aide de la disposition automatique, j'ai adapté la réponse de Chris Miles, mais en animant NSLayoutConstraints comme ceci:

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];
    }
}];
0
jay492355