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!
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.)
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
];
}
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];
}
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
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)
}
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.
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"];
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
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)
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:)];
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()
}
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()
}