web-dev-qa-db-fra.com

comment créer un effet d'icône oscillant sur un iphone?

Je souhaite faire osciller une image dans mon application de la même manière que les icônes de l'iPhone vacillent lorsque vous appuyez dessus. Quelle est la meilleure façon de faire ça? 

Ceci est ma première incursion dans des animations qui n'utilisent pas de GIF animé. Je pense que l'idée est de faire légèrement pivoter l'image d'avant en arrière pour créer l'effet de vacillement. J'ai déjà envisagé d'utiliser CABasicAnimation et CAKeyframeAnimation. CABasicAnimation crée une gigue chaque fois qu'elle se répète, car elle saute à la position De et n'interpole pas en arrière. CAKeyframeAnimation semble être la solution, sauf que je ne parviens pas à la faire fonctionner. J'ai dû louper quelque chose. Voici mon code utilisant CAKeyframeAnimation (qui ne fonctionne pas):

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


Ou il pourrait y avoir une solution totalement plus simple qui me manque. Apprécier tous les pointeurs. Merci!

51
jeanniey

Un moyen simple de le faire:

#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;
     }
}

Probablement avoir à jouer avec le timing et les angles, mais cela devrait vous aider à démarrer.

EDIT: J'ai modifié la réponse pour ajouter du code afin de remettre l'élément dans son état d'origine une fois l'opération terminée. Notez également que vous pouvez utiliser la valeur de contexte beginAnimations pour tout transmettre aux méthodes start/stop. Dans ce cas, il s'agit de l'objet wobbling lui-même. Vous n'avez donc pas à vous fier à des ivars spécifiques. La méthode peut être utilisée pour tout objet générique basé sur UIView (c'est-à-dire des étiquettes de texte, des images, etc.)

92
Ramin

La réponse de Ramin était très bonne, mais depuis OS4, le même effet peut être obtenu en utilisant animateWithDuration dans une fonction simple également.

(adapté son exemple pour les futurs 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
  ];
}
91
Pieter

Vous devez utiliser CAKeyframeAnimation pour créer une animation plus fluide.

+ (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];
}
14
docchang

J'ai écrit un exemple d'application qui tente de reproduire le mouvement d'icône et de mouvement de l'écran d'accueil: Exemple de code iPhone: Mosaïques

9
Kristopher Johnson

Pour les personnes qui ont récemment consulté cette publication et souhaitent faire de même dans Swift, voici ma traduction:

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)
}
3
Allen Conquest

Le moyen le plus simple que je connaisse est d'utiliser Core Animation. Fondamentalement, vous créez un bloc d'animation principale, puis effectuez une transformation et une rotation par rotation, puis répétez le compte. Core Animation s'occupe ensuite de tout ce qui est nécessaire pour obtenir cet effet de vacillement.

Pour démarrer un bloc Core Animation, procédez comme suit:

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

pas testé. Mais il se peut que vous deviez aussi faire:

[UIView setAnimationBeginsFromCurrentState:YES];

AUTANT QUE JE SACHE. setAnimationRepeatCount aura pour effet que l'animation sera terminée, annulée, terminée, annulée, terminée, annulée, terminée ... autant de fois que vous le spécifiez. Ainsi, vous voudrez peut-être commencer par pivoter vers la gauche sans nombre de répétitions, puis commencer à trembler avec le nombre de répétitions. Une fois terminé, vous souhaiterez peut-être revenir à la transformation d'identité (= aucune rotation et mise à l'échelle appliquées).

Vous pouvez chaîner des animations en définissant le délégué d'animation avec 

[UIView setAnimationDelegate:self]

et alors 

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

et dès que l'animation s'arrête, cette méthode sera appelée. Consultez la documentation de la classe UIView pour savoir comment implémenter cette méthode qui sera appelée à la fin de l'animation. Fondamentalement, à l’intérieur de cette méthode, vous exécuterez l’étape suivante (c’est-à-dire la rotation arrière ou toute autre chose), avec un nouveau bloc d’animation mais avec le même contexte et le même ID d’animation, puis, si nécessaire, spécifiez un autre didStopSelector.

METTRE À JOUR:

Vous voudrez peut-être vérifier:

[UIView setAnimationRepeatAutoreverses:YES];

cela va osciller d'avant en arrière automatiquement.

2
Thanks

Vous pouvez créer un effet de wobble sans fin en utilisant la variable CAKeyframeAnimation, comme suit:

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

En retard à la fête. J'utilise généralement le printemps avec amortissement (iOS7 et plus récent). Dans Swift, cela ressemble à:

    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
    }

Vous pouvez modifier l’effet en ajustant initialSpringVelocity et SpringWithDamping

1
EsbenB

Basé sur la réponse d'EsbenB, mais mis à jour pour Swift 3 et pour la rotation:

    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)
1
user599849

Bien, le code donné par Ramin fonctionne bien. Mais si vous utilisez l’application de barre de tabulation et passez à l’élément d’onglet suivant, puis revenez à l’élément d’onglet précédent, vous verrez que votre vue a été déplacée à gauche, à chaque fois.Alors La meilleure pratique consiste à utiliser la méthode ViewWillAppear en tant que.

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

de sorte que la vue à chaque fois est chargée, vous trouverez votre animation au bon endroit. Et utilisez également cette méthode.

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

Le moyen le plus simple d'y parvenir avec Swift 4+:

static func wobbleAnimation(button: UIButton) {
    CATransaction.begin()
    let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
    rotateAnimation.autoreverses = true
    rotateAnimation.repeatCount = Float.greatestFiniteMagnitude
    rotateAnimation.fromValue = CGFloat(-0.2)
    rotateAnimation.toValue = CGFloat(0.2)
    rotateAnimation.duration = 0.20
    button.layer.add(rotateAnimation, forKey: nil)
    CATransaction.commit()
}

static func stopWobbleAnimation(button: UIButton) {
    button.layer.removeAllAnimations()
}
0
Coder ACJHP

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()
}
0
Beslan Tularov