Animate Rotating UIImageView - iphone

I want to rotate a UIImageView by roughly 10 degrees left/right but have a smooth animation, rather than a sudden turn which I see using:
player.transform = CGAffineTransformMakeRotation(angle)

[UIView beginAnimations:nil context:NULL];
[UIView setAnimationDuration:0.25]; // Set how long your animation goes for
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
player.transform = CGAffineTransformMakeRotation(angle); // if angle is in radians
// if you want to use degrees instead of radians add the following above your #implementation
// #define degreesToRadians(x)(x * M_PI / 180)
// and change the above code to: player.transform = CGAffineTransformMakeRotation(degreesToRadians(angle));
[UIView commitAnimations];
// The rotation code above will rotate your object to the angle and not rotate beyond that.
// If you want to rotate the object again but continue from the current angle, use this instead:
// player.transform = CGAffineTransformRotate(player.transform, degreesToRadians(angle));

I use some code like this to achieve a similar effect (though I rotate it by 360 degrees):
CABasicAnimation *rotate;
rotate = [CABasicAnimation animationWithKeyPath:#"transform.rotation"];
rotate.fromValue = [NSNumber numberWithFloat:0];
rotate.toValue = [NSNumber numberWithFloat:deg2rad(10)];
rotate.duration = 0.25;
rotate.repeatCount = 1;
[self.view.layer addAnimation:rotate forKey:#"10"];
I use code very similar to this to spin an image view.

Converted for Swift 3/4:
let animation = CABasicAnimation(keyPath: "transform.rotation")
animation.fromValue = 0
animation.toValue = Double.pi * 2.0
animation.duration = 2
animation.repeatCount = .infinity
animation.isRemovedOnCompletion = false
imageView.layer.add(animation, forKey: "spin")

i have some code rotate 360 degrees:
- (void) runSpinAnimationOnView:(UIView*)view duration:(CGFloat)duration rotations:(CGFloat)rotations repeat:(float)repeat;
{
CABasicAnimation* rotationAnimation;
rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
rotationAnimation.toValue = [NSNumber numberWithFloat: rotations * 2.0 /* full rotation*/ * rotations * duration ];
rotationAnimation.duration = duration;
rotationAnimation.cumulative = YES;
rotationAnimation.repeatCount = repeat;
[view.layer addAnimation:rotationAnimation forKey:#"rotationAnimation"];
}
call it: [self runSpinAnimationOnView:imgView duration:0.1 rotations:M_PI_4 repeat:10];
And animation rotate some degrees and again:
- (void)rotateImage:(UIImageView *)image duration:(NSTimeInterval)duration delay:(NSTimeInterval)delay
curve:(int)curve rotations:(CGFloat)rotations
{
[UIView animateWithDuration:duration
delay:delay
options:0
animations:^{
[UIView setAnimationCurve:curve];
image.transform = CGAffineTransformMakeRotation(rotations);
}
completion:^(BOOL finished){
[self rotateImage2:image duration:duration delay:delay curve:curve rotations:rotations];
;
}];
[UIView commitAnimations];
}
- (void)rotateImage2:(UIImageView *)image duration:(NSTimeInterval)duration delay:(NSTimeInterval)delay
curve:(int)curve rotations:(CGFloat)rotations
{
[UIView animateWithDuration:duration
delay:delay
options:0
animations:^{
[UIView setAnimationCurve:curve];
image.transform = CGAffineTransformMakeRotation(-rotations);
}
completion:^(BOOL finished){
[self rotateImage:image duration:duration delay:delay curve:curve rotations:rotations];
;
}];
[UIView commitAnimations];
}
If you want to rotate a uiimageview by roughly 10 degrees left/right
call : [self rotateImage:imgView duration:0.7 delay:0.0 curve:UIViewAnimationCurveEaseIn rotations:(M_PI/18)];
similar, some degress

A modern Swift solution, using NSTimer and CGAffineTransformMakeRotation:
class Rotation: UIViewController {
var timer = NSTimer()
var rotAngle: CGFloat = 0.0
#IBOutlet weak var rotationImage: UIImageView!
override func viewDidLoad() {
super.viewDidLoad()
activateTimer()
}
func activateTimer() {
//(1)
timer = NSTimer.scheduledTimerWithTimeInterval(1.0, target:self, selector:#selector(Rotation.updateCounter), userInfo: nil, repeats: true)
}
func updateCounter() {
//(2)
var rotateLeft: Bool = true
if rotateLeft {
rotAngle -= 30.0
} else {
rotAngle += 30.0
}
//(3)
UIView.animateWithDuration(2.0, animations: {
self.rotationImage.transform = CGAffineTransformMakeRotation((self.rotAngle * CGFloat(M_PI)) / 180.0)
})
}
}
Things to notice:
Connect the outlet the the UIImageView
Play with the (1)timer pace, (2)animation pace and (3)rotation angle to get the desired results. This set of variables worked for me.

Related

Rotate a UIView clockwise for an angle greater than 180 degrees

I'm animating a clock arm from pointing towards 12 o'clock to the current time. If it is say, 11 o'clock, I want the arm to rotate clockwise to the 11 o'clock position. But of course if I use:
CGAffineTransform rotation = CGAffineTransformMakeRotation(2*M_PI*11/12);
[UIView animateWithDuration:3.0
animations:^{
clockArm.transform = rotation;
}];
the rotation goes counterclockwise. I tried:
CGFloat angle = 2*M_PI*11/12;
CGAffineTransform firstRotation = CGAffineTransformMakeRotation(M_PI-0.001);
CGFloat firstRotationTime = 3.0*(M_PI/angle);
CGAffineTransform secondRotation = CGAffineTransformMakeRotation(angle);
CGFloat secondRotationTime = 3.0 - firstRotationTime;
[UIView animateWithDuration:firstRotationTime
delay:0.0
options:UIViewAnimationCurveLinear
animations:^{
self.clockArm1.transform = firstRotation;
}
completion:^(BOOL finished) {
[UIView animateWithDuration:secondRotationTime
delay:0.0
options:UIViewAnimationCurveLinear
animations:^{
self.clockArm1.transform = secondRotation;
}
completion:^(BOOL finished){
}];
}];
The animation does go clockwise, but it is choppy - the animation still seems to be doing a UIViewAnimationEaseInOut for the first animation. What am I doing wrong, or is there another way to accomplish what I want?
You can use the completion block of CATransaction to set the rotation property of the view when the animation has finished. The following function worked in my test case:
- (void) rotateViewAnimated:(UIView*)view
withDuration:(CFTimeInterval)duration
byAngle:(CGFloat)angle
{
[CATransaction begin];
CABasicAnimation *rotationAnimation;
rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
rotationAnimation.byValue = [NSNumber numberWithFloat:angle];
rotationAnimation.duration = duration;
rotationAnimation.removedOnCompletion = YES;
[CATransaction setCompletionBlock:^{
view.transform = CGAffineTransformRotate(view.transform, angle);
}];
[view.layer addAnimation:rotationAnimation forKey:#"rotationAnimation"];
[CATransaction commit];
}
You use it like
[self rotateViewAnimated:self.clockArm1 withDuration:3.0 byAngle:2*M_PI*11./12.];
Thanks for #Martin R 's accepted answer. I edited a bit:
I replaced this line
rotationAnimation.removedOnCompletion = YES;
with these two lines:
rotationAnimation.removedOnCompletion = NO;
rotationAnimation.fillMode = kCAFillModeForwards;
Then, in the CompletionBlock, I added one more line:
[view.layer removeAllAnimations];
So, the code finally becomes:
- (void) rotateViewAnimated:(UIView*)view
withDuration:(CFTimeInterval)duration
byAngle:(CGFloat)angle
{
[CATransaction begin];
CABasicAnimation *rotationAnimation;
rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
rotationAnimation.byValue = [NSNumber numberWithFloat:angle];
rotationAnimation.duration = duration;
// changed by me
rotationAnimation.removedOnCompletion = NO;
rotationAnimation.fillMode = kCAFillModeForwards;
[CATransaction setCompletionBlock:^{
view.transform = CGAffineTransformRotate(view.transform, angle);
// added by me
[view.layer removeAllAnimations]; // this is important
}];
[view.layer addAnimation:rotationAnimation forKey:#"rotationAnimation"];
[CATransaction commit];
}
My references are from:
1, https://stackoverflow.com/a/3586433/2481444
2, After rotating a CALayer using CABasicAnimation the layer jumps back to it's unrotated position
As mentioned in comments, try this iphone UIImageView rotation
or UIView Infinite 360 degree rotation animation?
#import <QuartzCore/QuartzCore.h>
- (void) runSpinAnimationWithDuration:(CGFloat) duration;
{
CABasicAnimation* rotationAnimation;
rotationAnimation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
rotationAnimation.toValue = [NSNumber numberWithFloat: M_PI * 2.0 /* full rotation*/ * rotations * duration ];
rotationAnimation.duration = duration;
rotationAnimation.cumulative = YES;
rotationAnimation.repeatCount = 1.0;
rotationAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
[myView.layer addAnimation:rotationAnimation forKey:#"rotationAnimation"];
}

UIVIew Flip Vertical Animation

Given:
[UIView beginAnimations:nil context:nil];
[UIView setAnimationCurve:UIViewAnimationCurveEaseOut];
[UIView setAnimationDuration:.5];
[UIView setAnimationTransition:UIViewAnimationTransitionFlipFromRight forView:card cache:NO];
myPic = [UIImage UIImagenamed: #"mySecondImage.png"];
[UIView commitAnimations];[/CODE]
Which animates 'myPic' right to left with a flip.
I need to get the same animation, but vertically. Flip from Top or Flip from Bottom. I looked around, no one really had a working model suggested.
I tried this, yet, no luck:
float duration = .5;
CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.x"];
animation.fromValue = [NSNumber numberWithDouble:0.0f * M_PI];
animation.toValue = [NSNumber numberWithDouble:1.0f * M_PI];
animation.duration = duration;
animation.removedOnCompletion = NO;
animation.fillMode = kCAFillModeBoth;
animation.repeatCount =1;;
animation.beginTime = CACurrentMediaTime();
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
card.layer.anchorPoint = CGPointMake(0.5, 1.0);
[card.layer addAnimation:animation forKey:#"rotationX"];[/CODE]
Any input?
Thanks in advance.
I've also needed flip from bottom animation. I've compiled couple solutions and this works for me
- (CATransform3D) makeRotationAndPerspectiveTransform:(CGFloat) angle {
CATransform3D transform = CATransform3DMakeRotation(angle, 1.0f, 0.0f, 0.0f);
transform.m34 = 1.0 / -500;
return transform;
}
- (void) flipFromBottom {
//setup firstView to be visible
view1.layer.transform = CATransform3DMakeRotation(0, 1.0f, 0.0f, 0.0f);
view1.hidden = NO;
// setup secondView to be partialy rotated and invisible
view2.layer.transform = [self makeRotationAndPerspectiveTransform:M_PI/2];
view2.hidden = YES;
// making sure that both views have the same position
view2.frame = view1.frame;
CFTimeInterval duration = 2.0;
[UIView animateWithDuration:duration/2
delay:0
options:UIViewAnimationCurveEaseIn
animations:^{
view1.layer.transform = [self makeRotationAndPerspectiveTransform:-M_PI / 2];
}
completion:^(BOOL finished){
view1.hidden = YES;
view2.hidden = NO;
[UIView animateWithDuration:duration /2
delay:0
options:UIViewAnimationCurveEaseOut
animations:^{
view2.layer.transform = CATransform3DMakeRotation(0.0f, 1.0f, 0.0f, 0.0f);
}
completion:NULL];
}];
}

How to let a view rotate forever?

Is there a way to let a view rotate forever, with an specified speed? I need that for an indicator kind of thing. I know there is this weird Lxxxxx00ff constant (don't remember it exactly) that stands for "forever".
You can use HUGE_VAL for floating value (if I remember correctly, repeatCount property for animation is a float).
To setup animation you can create CAAnimation object using +animationWithKeyPath: method:
CABasicAnimation* animation = [CABasicAnimation animationWithKeyPath:#"transform.rotation.z"];
animation.fromValue = [NSNumber numberWithFloat:0.0f];
animation.toValue = [NSNumber numberWithFloat: 2*M_PI];
animation.duration = 3.0f;
animation.repeatCount = HUGE_VAL;
[rotView.layer addAnimation:animation forKey:#"MyAnimation"];
If I remember correctly creating this kind of rotation using just UIView animations is impossible because rotations on 360 degrees (2*M_PI radians) are optimized to no rotation at all.
Edit: Added a Swift version.
let animation = CABasicAnimation(keyPath: "transform.rotation.z")
animation.fromValue = NSNumber(value: 0.0)
animation.toValue = NSNumber(value: 2*Double.pi)
animation.duration = 3.0
animation.repeatCount = Float.greatestFiniteMagnitude
rotView.layer.add(animation, forKey: "MyAnimation")
my solution for this is a bit hacky since it doesn't use core animation but at least it works truly forever and doesn't require you to setup multiple animation steps.
...
// runs at 25 fps
NSTimer* timer = [NSTimer scheduledTimerWithTimeInterval:1.0/25
target:self
selector:#selector(rotate)
userInfo:nil
repeats:YES];
[timer fire];
...
- (void)rotate {
static int rotation = 0;
// assuming one whole rotation per second
rotation += 360.0 / 25.0;
if (rotation > 360.0) {
rotation -= 360.0;
}
animatedView.transform = CGAffineTransformMakeRotation(rotation * M_PI / 180.0);
}
my bet is:
-(void)animationDidStopSelector:... {
[UIView beginAnimations:nil context:NULL];
// you can change next 2 settings to setAnimationRepeatCount and set it to CGFLOAT_MAX
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:#selector(animationDidStopSelector:...)];
[UIView setAnimationDuration:...
[view setTransform: CGAffineTransformRotate(CGAffineTransformIdentity, 6.28318531)];
[UIView commitAnimations];
}
//start rotation
[self animationDidStopSelector:...];
ok better bet:
[UIView beginAnimations:nil context:NULL];
[UIView setAnimationRepeatCount: CGFLOAT_MAX];
[UIView setAnimationDuration:2.0f];
[view setTransform: CGAffineTransformMakeRotation(6.28318531)];
[UIView commitAnimations];

Shake visual effect on iPhone (NOT shaking the device)

On login failure, I'd prefer to avoid showing an alert, it's too fleeting. Showing the alert and then showing the text somewhere on the login screen seems like duplication.
So I'd like for it to graphically shake my login view when the user enters the wrong user ID and password like the Mac login screen does.
Anyone know if there's a way to pull this off, or have any suggestions for another effect I could use?
I think this is a more efficient solution:
Swift:
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 ] ;
Only one animation object is created and it's all performed at the CoreAnimation level.
Using iOS 4+ block based UIKit animations (and loosely based on on jayccrown's answer):
- (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];
}
}];
}
I had seen some wobble animation and changed it to shake a view t pixels upright and downleft:
- (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;
}
}
Here's a tutorial that details how to do it in Cocoa. Should be the same for the iPhone (or at least quite similar).
http://www.cimgf.com/2008/02/27/core-animation-tutorial-window-shake-effect/
Simply changing the X coordinate of the center property of your view might do the trick. If you haven't done any core animation before it's pretty straight-forward.
First, start an animation right, then listen for it to finish, and then move back to the left, and so on. Getting the timing down so it "feels right" might take a while.
- (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];
}
}
This UIView category snippet worked for me. It's using 3 CABasingAnimations applied to view's layer.
#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
Hope it helpes someone :)
In iOS 7.0 or later, UIKit keyframe animation is available.
[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];
very easy shake categorie for UIVoew
https://github.com/jonasschnelli/UIView-I7ShakeAnimation
I know the question is already answered, but since I have already implemented something like this previously, I feel it can't hurt to add it:
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"];
Also, since you want the shaking to indicate that the user failed to log in, you might also consider adding this animation that tints the screen red while the screen shakes:
//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"];
Hope this helps!
Using Auto Layout, I adapted Chris Miles' answer but animated NSLayoutConstraints like this:
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];
}
}];
A solution I used for constraints which I set in my storyboard. Without using 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
}
}
In my case the animation just kept on going, but I pop my viewcontroller after a duration of a few seconds, this stops my timer aswell.

how to create iphone's wobbling icon effect?

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!
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'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
];
}
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];
}
I have written a sample app that attempts to replicate the home screen wobble and icon movement: iPhone Sample Code: Tiles
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)
}
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.
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"];
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)
}
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
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)
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:)];
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()
}
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()
}