how to create iphone's wobbling icon effect?
IphoneIosAnimationRotationIphone Problem Overview
I want to wobble an image back and forth in my application similar to how the iPhone icons wobble when you press down on it. What's the best way to do that?
This is my first foray into animations that's not using an animated GIF. I think the idea is to slightly rotate the image back and forth to create the wobbling effect. I've looked at using CABasicAnimation and CAKeyframeAnimation. CABasicAnimation creates a jitter every time it repeats because it jumps to the from position and doesn't interpolate back. CAKeyframeAnimation seems like the solution except that I can't get it to work. I must be missing something. Here's my code using the CAKeyframeAnimation (which doesn't work):
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];
Or there could be a totally simpler solution that I'm just missing. Appreciate any pointers. Thanks!
Iphone Solutions
Solution 1 - Iphone
Simple way to do it:
#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;
}
}
Probably have to play with timing and angles but this should get you started.
EDIT: I edited the response to add code to put the item back in its original state when done. Also, note that you can use the beginAnimations
context value to pass along anything to the start/stop methods. In this case it's the wobbling object itself so you don't have to rely on specific ivars and the method can be used for any generic UIView-based object (i.e. text labels, images, etc.)
Solution 2 - Iphone
Ramin's answer was very good, but since OS4 the same effect can be achieved using animateWithDuration in one simple function too.
(adapted his example for future 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
];
}
Solution 3 - Iphone
You should use CAKeyframeAnimation
to make a smoother animation.
+ (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];
}
Solution 4 - Iphone
I have written a sample app that attempts to replicate the home screen wobble and icon movement: iPhone Sample Code: Tiles
Solution 5 - Iphone
For anyone who has come across this posting more recently and would like to do the same in Swift, here is my translation:
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)
}
Solution 6 - Iphone
The easiest way I know is to use Core Animation. Basically, you create an Core Animation Block, then do an rotation transform and setup and repeat count. Core Animation then takes care of everything that's needed to do this wobbling effect.
To start an Core Animation block, just do:
[UIView beginAnimations:@"any string as animationID" context:self];
[UIView setAnimationRepeatCount:10];
// rotate
[UIView commitAnimations];
not tested. But it can be that you will also have to do:
[UIView setAnimationBeginsFromCurrentState:YES];
A.F.A.I.K. setAnimationRepeatCount will have the effect that the animation gets done, undone, done, undone, done, undone, done... as many times as you specify. So you may want to first rotate to left with no repeat count, and then from this point start wobbling with repeat count. When done, you may want to rotate back to the identity transform (= no rotation and scaling applied).
You can chain animations by setting the animation delegate with
[UIView setAnimationDelegate:self]
and then
[UIView setAnimationDidStopSelector:@selector(myMethod:finished:context:)];
and as soon as the animation stops, that method will be called. See the UIView class documentation for how to implement that method that will be called when the animation stops. Basically, inside that method you would perform the next step (i.e. rotating back, or anything else), with an new animation block but same context and animation ID, and then (if needed) specify another didStopSelector.
UPDATE:
You may want to check out:
[UIView setAnimationRepeatAutoreverses:YES];
this will wobble back and forth automatically.
Solution 7 - Iphone
You can create a not-endless wobble effect using the CAKeyframeAnimation
, like so:
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"];
Solution 8 - Iphone
from above answers, I got the swift5 version:
func startWobble() {
let angle = 5.0 * Double.pi / 180.0;
self.transform = CGAffineTransform.identity.rotated(by: CGFloat(-angle));
UIView.animate(withDuration: 0.25, delay: 0, options: [.allowUserInteraction,.repeat,.autoreverse], animations: {
self.transform = CGAffineTransform.identity.rotated(by: CGFloat(angle));
}, completion: nil)
}
func stopWobble() {
UIView.animate(withDuration: 0.25, delay: 0, options: [.allowUserInteraction,.beginFromCurrentState,.curveLinear], animations: {
self.transform = CGAffineTransform.identity;
}, completion: nil)
}
Solution 9 - Iphone
Late to the party. I'm typically using spring with damping (iOS7 and newer). In swift it looks like:
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
}
You can tweak the effect by adjusting the initialSpringVelocity
and SpringWithDamping
Solution 10 - Iphone
Based on EsbenB's answer, but updated for Swift 3 and for 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)
Solution 11 - Iphone
Well the code given by Ramin works well.But if you use tabbar application and move to next tab item then again come back to previous tab item,you will see that your view has been moved to left,every time.So the best practice is that you use ViewWillAppear method as.
- (void)viewWillAppear:(BOOL)animated
{
UIView* item = self.view;
item.transform = CGAffineTransformIdentity;
}
so the every time view is loaded you will find you animation at the right place.And also use this method as well.
[UIView setAnimationDidStopSelector:@selector(myMethod:finished:context:)];
Solution 12 - Iphone
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()
}
Solution 13 - Iphone
Easiest way to this with 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()
}