views:

4593

answers:

3

Hello,

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!

+2  A: 

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.

Thanks
thanks Thanks! can i just setup a chain of animation blocks one after the other in my function without using the DidStopSelector? are there any best practices or performance considerations?
jeanniey
No. I think you need to use the DidStopSelector. Unfortunately, that looks a little unhandy in code, since you will have to create a method for every anymation phase. But you can re-use them if your animation consists of simple phases like "wobble to left", "wobble to center" and "wobble to right". If you don't like to have more than one method for that, you could specify the method itself as the didStopSelector. But that would make things more complicated. I typically set up a series of methods one after another to chain them. And when you're done, set the didStopSelector to nil.
Thanks
Updated: See [UIView setAnimationRepeatAutoreverses:YES];
Thanks
You're right, the DidStopSelector is necessary. Thanks!
jeanniey
+23  A: 

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.)

Ramin
this works very well, but the image ends up in a rotated state when i want it to end back upright in the original state. do you know how that can be done in the animation block itself? must i use the animationDidStopSelector to reset position?
jeanniey
I edited the response to show how that could be done.
Ramin
Nice! I haven't tried it, but it looks like if the view would abrupt start with an rotated transform, and abrupt end from an rotated transform to the identity transform (= no rotation). So you'll need some more code here to get it right, i.e. make 3 phases. phase 1) rotate animated to leftWobble, phase 2) do that animation stuff, phase 3) rotate animated back to identity transform.
Thanks
Thanks! This works great for what I need. If going for a smoother wobble, the 3 phases look like it would work, although would it make sense to use the CAKeyframeAnimation then?
jeanniey
Yes. For more complex multiphase animations--especially if there are timing constraints--core animation is easier to manage and maintain than a chain of UIView animations. UIView is better for 'set and forget' type operations especially when it comes to simple movement, scaling, and rotation. It's also handy for changing view properties over time, like alpha and color values.
Ramin
This was very helpful. Question: Is there a way to have this animation run infinitely UNTIL the user taps on the view?
wgpubs
You can set the RepeatCount to a very high number to have it run for a long time. To get a user tap, override the 'touchesEnded' method on the UIView and stop the animation.
Ramin
Wow :) (awesome) ossam (not appended a :) )
sugar
A: 

A note for using this function - if you use IB, and a UIButton, you must set the image as "image", bot as "background". It will then work like a charm!

iPhone Guy