UIView Infinite 360 degree rotation animation?
Solution 1
Found a method (I modified it a bit) that worked perfectly for me: iphone UIImageView rotation
#import <QuartzCore/QuartzCore.h>
- (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: M_PI * 2.0 /* full rotation*/ * rotations * duration ];
rotationAnimation.duration = duration;
rotationAnimation.cumulative = YES;
rotationAnimation.repeatCount = repeat ? HUGE_VALF : 0;
[view.layer addAnimation:rotationAnimation forKey:@"rotationAnimation"];
}
Solution 2
Kudos to Richard J. Ross III for the idea, but I found that his code wasn't quite what I needed. The default for options
, I believe, is to give you UIViewAnimationOptionCurveEaseInOut
, which doesn't look right in a continuous animation. Also, I added a check so that I could stop my animation at an even quarter turn if I needed (not infinite, but of indefinite duration), and made the acceleration ramp up during the first 90 degrees, and decelerate during the last 90 degrees (after a stop has been requested):
// an ivar for your class:
BOOL animating;
- (void)spinWithOptions:(UIViewAnimationOptions)options {
// this spin completes 360 degrees every 2 seconds
[UIView animateWithDuration:0.5
delay:0
options:options
animations:^{
self.imageToMove.transform = CGAffineTransformRotate(imageToMove.transform, M_PI / 2);
}
completion:^(BOOL finished) {
if (finished) {
if (animating) {
// if flag still set, keep spinning with constant speed
[self spinWithOptions: UIViewAnimationOptionCurveLinear];
} else if (options != UIViewAnimationOptionCurveEaseOut) {
// one last spin, with deceleration
[self spinWithOptions: UIViewAnimationOptionCurveEaseOut];
}
}
}];
}
- (void)startSpin {
if (!animating) {
animating = YES;
[self spinWithOptions: UIViewAnimationOptionCurveEaseIn];
}
}
- (void)stopSpin {
// set the flag to stop spinning after one last 90 degree increment
animating = NO;
}
Update
I added the ability to handle requests to start spinning again (startSpin
), while the previous spin is winding down (completing). Sample project here on Github.
Solution 3
In Swift, you can use the following code for infinite rotation:
Swift 4
extension UIView {
private static let kRotationAnimationKey = "rotationanimationkey"
func rotate(duration: Double = 1) {
if layer.animation(forKey: UIView.kRotationAnimationKey) == nil {
let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotationAnimation.fromValue = 0.0
rotationAnimation.toValue = Float.pi * 2.0
rotationAnimation.duration = duration
rotationAnimation.repeatCount = Float.infinity
layer.add(rotationAnimation, forKey: UIView.kRotationAnimationKey)
}
}
func stopRotating() {
if layer.animation(forKey: UIView.kRotationAnimationKey) != nil {
layer.removeAnimation(forKey: UIView.kRotationAnimationKey)
}
}
}
Swift 3
let kRotationAnimationKey = "com.myapplication.rotationanimationkey" // Any key
func rotateView(view: UIView, duration: Double = 1) {
if view.layer.animationForKey(kRotationAnimationKey) == nil {
let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation")
rotationAnimation.fromValue = 0.0
rotationAnimation.toValue = Float(M_PI * 2.0)
rotationAnimation.duration = duration
rotationAnimation.repeatCount = Float.infinity
view.layer.addAnimation(rotationAnimation, forKey: kRotationAnimationKey)
}
}
Stopping is like:
func stopRotatingView(view: UIView) {
if view.layer.animationForKey(kRotationAnimationKey) != nil {
view.layer.removeAnimationForKey(kRotationAnimationKey)
}
}
Solution 4
Nate's answer above is ideal for stop and start animation and gives a better control. I was intrigued why yours didn't work and his does. I wanted to share my findings here and a simpler version of the code that would animate a UIView continuously without stalling.
This is the code I used,
- (void)rotateImageView
{
[UIView animateWithDuration:1 delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
[self.imageView setTransform:CGAffineTransformRotate(self.imageView.transform, M_PI_2)];
}completion:^(BOOL finished){
if (finished) {
[self rotateImageView];
}
}];
}
I used 'CGAffineTransformRotate' instead of 'CGAffineTransformMakeRotation' because the former returns the result which is saved as the animation proceeds. This will prevent the jumping or resetting of the view during the animation.
Another thing is not to use 'UIViewAnimationOptionRepeat' because at the end of the animation before it starts repeating, it resets the transform making the view jump back to its original position. Instead of a repeat, you recurse so that the transform is never reset to the original value because the animation block virtually never ends.
And the last thing is, you have to transform the view in steps of 90 degrees (M_PI / 2) instead of 360 or 180 degrees (2*M_PI or M_PI). Because transformation occurs as a matrix multiplication of sine and cosine values.
t' = [ cos(angle) sin(angle) -sin(angle) cos(angle) 0 0 ] * t
So, say if you use 180-degree transformation, the cosine of 180 yields -1 making the view transform in opposite direction each time (Note-Nate's answer will also have this issue if you change the radian value of transformation to M_PI). A 360-degree transformation is simply asking the view to remain where it was, hence you don't see any rotation at all.
Solution 5
My contribution with a Swift Extension from the checked solution :
Swift 4.0
extension UIView{
func rotate() {
let rotation : CABasicAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotation.toValue = NSNumber(value: Double.pi * 2)
rotation.duration = 1
rotation.isCumulative = true
rotation.repeatCount = Float.greatestFiniteMagnitude
self.layer.add(rotation, forKey: "rotationAnimation")
}
}
Deprecated :
extension UIView{
func rotate() {
let rotation : CABasicAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotation.toValue = NSNumber(double: M_PI * 2)
rotation.duration = 1
rotation.cumulative = true
rotation.repeatCount = FLT_MAX
self.layer.addAnimation(rotation, forKey: "rotationAnimation")
}
}
Related videos on Youtube
Derek
Updated on August 26, 2022Comments
-
Derek over 1 year
I'm trying to rotate a
UIImageView
360 degrees, and have looked at several tutorials online. I could get none of them working, without theUIView
either stopping, or jumping to a new position.- How can I achieve this?
The latest thing I've tried is:
[UIView animateWithDuration:1.0 delay:0.0 options:0 animations:^{ imageToMove.transform = CGAffineTransformMakeRotation(M_PI); } completion:^(BOOL finished){ NSLog(@"Done!"); }];
But if I use 2*pi, it doesn't move at all (since it's the same position). If I try to do just pi (180 degrees), it works, but if I call the method again, it rotates backwards.
EDIT:
[UIView animateWithDuration:1.0 delay:0.0 options:0 animations:^{ [UIView setAnimationRepeatCount:HUGE_VALF]; [UIView setAnimationBeginsFromCurrentState:YES]; imageToMove.transform = CGAffineTransformMakeRotation(M_PI); } completion:^(BOOL finished){ NSLog(@"Done!"); }];
doesn't work either. It goes to
180
degrees, pauses for a split second, then resets back to0
degrees before it starts again. -
Derek about 12 yearsI am very new to blocks but this method throws the errror "Incompatible block pointer types sending 'void(^const__strong()' to parameter of type 'void(^)(BOOL)'. I tried to change my rotation method in the code in my question to the imageToMove.transform = CGAffineTransformRotate(imageToMove.transform, M_PI / 2); you used, and the animation still has a slight delay, and resets before the next turn.
-
AlBeebe almost 12 years#import <QuartzCore/QuartzCore.h>
-
Andrew Hoyer over 11 yearsThanks for the clear explanation and code. I had this same basic code, but your values made it fall into place.
-
gjpc over 11 yearsadd QuartzCore.framework Project->Build Phases->Link Binaries
-
PaulWoodIII almost 11 yearsThis is the right code for iOS 3.0 and below but for newer programmers and new projects, Apple now warns users away from these methods in the Docs & @Nate code uses the block based animations that Apple now prefers
-
kbtz almost 11 years@Antoine what
cumulative
actually means? -
alecail almost 11 yearsWould this method (recursive call on completion) work well with a fast changing animation ? For example, the duration would be approx. 1/30s so that I can modify the rotation speed of the object in real time, reacting to touches for example ?
-
Nate almost 11 yearsThe whole point of my answer (and Richard's answer that it derives from) is to use 90 degree steps, to avoid switching directions. 90 degrees also works relatively well if you do ever want to stop the rotation, since many images have either 180 degree, or 90 degree symmetry.
-
ram almost 11 yearsYes, I just thought I would explain why it is being used :)
-
smad over 10 years@cvsguimaraes From the doc: "Determines if the value of the property is the value at the end of the previous repeat cycle, plus the value of the current repeat cycle."
-
Nikita over 10 yearscan be there stack overflow?
-
huggie over 10 years@nik But after setting breakpoint in there I see that there wouldn't be stack overflow because the completion block is called by the system, by that time
rotateImageView
has already finished. So it's not really recursive in that sense. -
Eliot over 10 yearsAlso in this method, changing the
duration:
parameter seems to have no effect since it affects both the actualrotationAnimation.duration
and thetoValue
. I fixed this in my code by removingduration
as a coefficient in thetoValue
calculation. -
nielsbot over 10 yearsThis works for me w/o cumulative. I set
toValue = @(2.0 * M_PI)
(nofromValue
) -
Pradeep Mittal about 10 yearsGood answer +1 for a small version of code with desired functionality.
-
Arjun Mehta about 10 yearsUsing this, I notice a brief pause in the animation at each PI/2 angle (90 degrees) and a marginal increase in CPU usage over the chosen answer using CABasicAnimation. The CABasicAnimation method produces a flawlessly smooth animation.
-
Arjun Mehta about 10 yearsI don't understand why this method isn't preferred for newer programmers. Can someone explain? It's more smooth and uses slightly less CPU than the other block-based answers below.
-
Maverick almost 10 yearsThis simple and nice solution saved my day :)
-
CalZone almost 10 yearsCould be a dumb question, is it not a better idea to do it in a block? Keep animation separate from main thread?
-
Dilip almost 10 yearsHow to change spin direction in this code. nice code +1
-
Nate almost 10 years@Dilip, replace
M_PI / 2
with- (M_PI / 2)
. (Scroll code to the right to see the end of each line) -
arunit21 over 9 yearsThis might help some one, [view.layer removeAllAnimations]; to stop animation when needed.
-
Jonathan Zhan over 9 yearsI really appreciated the explanation of why to not use UIViewAnimationOptionRepeat.
-
Dilip over 9 yearsHi Nate, I am stoping this animation after 0.35 second, but some time images not be in correct orientation, It will stop at different angle. Do you have solution for that.
-
Nate over 9 years@Dilip, I don't know what duration you're using in your code. The same
0.5
seconds per 90 degrees, as I am? If so, my code doesn't let you stop at a fraction of a 90 degree rotation. It allows you to stop at a whole number of 90 degree intervals, but adds one extra 90 degree rotation, "eased out". So, in the code above, if you callstopSpin
after 0.35 seconds, it will spin for another 0.65 seconds, and stop at 180 degrees total rotation. If you have more detailed questions, you should probably open a new question. Feel free to link to this answer. -
Alex Pretzlav over 9 yearsThis only rotated 1/4 of a rotation for me.
-
levigroker over 9 years@AlexPretzlav Be sure you have
UIViewAnimationOptionRepeat
set in your animationoptions
. -
Alex Pretzlav over 9 yearsI did, it rotates 45°, and then loops by jumping back to 0° and rotating 45° again
-
levigroker over 9 yearsUpdated my answer with new information about why this "worked"
-
Nate over 9 yearsWhat are you seeing happen, @MohitJethwa? I have an app that uses this, and it still works for me on iOS 8.1.
-
mattsven about 9 yearsPerfect. Worked better than the above answers.
-
Dilip almost 9 years@BreadicalMD, Math is like programming, You can do same thing with multiple way. That doesn't mean that only 1 way is correct and other are not. Yes there can be some pros and cons for every way, But that doesn't means you can question on someones programming method, You can suggest cons for this method and pros for your method. This comment is really offensive, and you should not add comment like this.
-
BreadicalMD almost 9 yearsI'm sorry it offended you Dilip, but writing 2*M_PI is objectively more clear than ((360 * M_PI)/180), and writing the latter demonstrates a lack of understanding of the subject at hand.
-
Dilip almost 9 years@BreadicalMD No worries, but its good suggestion, i have updated my code. Thanx.
-
mattsven almost 9 yearsThis had odd effects when I used it in iOS 8. It actually negatively impacts the performance of UIWebView, maybe other views. I'm not sure why.
-
tony.stack over 8 yearsI found it was spinning TOO FAST for my tastes so I added: rotationAnimation.speed = 0.06; - hope that helps someone!
-
Vivek Shah over 8 years@tony.stack I also want to change the speed. Thanks for your comment
-
Luda over 8 yearsWhat is the kRotationAnimationKey?
-
Kádi over 8 yearsIt is a constant String key, that helps you identify and search the animation. It can be any String you want. In the removal process, you can see that the animation is searched and than deleted by this key.
-
Luda over 8 yearsIs it any string or something specific?
-
Kádi over 8 yearsSorry for late answer, but yes, it can be any string.
-
byJeevan about 8 yearsIt is not happening for infinite times !
-
user2526811 over 7 yearshow can I stop rotation?
-
yoninja over 7 yearsHow about you put a flag? So, maybe you have a func for stopping the animation:
var stop = false
private func stopRotation() { stop = true }
Then, insideif finished {...}
:if finished { if stop { return} self.rotateImageView() }
-
user2526811 over 7 yearsThank you, I exactly did the same. Thanks for your response.
-
NGG over 7 years@snolflake, or anybody, when I use this for rotating my image view, I just rotate it 180, but after the animation, the image is coming back to the previews orientation. how can I "save" the new orientation, is it "cumulative"? cause it doesn't work..
-
kbtz over 7 years@NGG Sorry I can't remember, but that was the answer I received: "Determines if the value of the property is the value at the end of the previous repeat cycle, plus the value of the current repeat cycle."
-
Iulian Onofrei over 7 yearsIsn't this ... not infinite?
-
Nate over 7 years@IulianOnofrei, can you elaborate on that? I'm using it on a non-symmetric image. I mention in the answer that the rotation stops at 90 degree increments, but that's not the same as requiring symmetry.
-
Iulian Onofrei over 7 yearsIf the image rotated at 90 degrees is not the same as the image without rotation, the animation end-start jump will be noticed.
-
Nate over 7 years@IulianOnofrei, have you tried the sample on Github? I just ran it, and it definitely does not jump. Why would it? The line
self.imageToMove.transform = CGAffineTransformRotate(imageToMove.transform, M_PI / 2)
is always taking the current transform and adding 90. In any case, this seems like a rough downvote, as the question didn't even mention symmetry. -
Zander Zhang about 7 years// Any key as in the answer
-
Harish J about 7 yearsrotationAnimation.repeatCount = HUGE_VALF to spin infinitely... When you are done, stop by calling another function like this: - (void) stopSpinAnimationOnView:(UIView*)view { [view.layer removeAnimationForKey:@"rotationAnimation"]; }
-
Zgpeace almost 7 yearsWell done. I just want to comment on the UIImageView category. Thank you all the same.
-
ihsan Khan almost 7 years@Kádi how can we get the from value? let say we stop the animation at a specific point and then we want to start it from that point then what should be the fromValue? can you please help me?
-
Hemang over 6 yearsIs there a way to spin it 360 degrees horizontally/vertically?
-
Nate over 6 years@Hemang, sure, but that's not what this question was. I'd post a new question if this is what you're trying to do.
-
Stephen J over 6 years@ihsanKhan you'd grab the Presentation Layer. Also this answer forgot the easing curve. The Animation Programming Guide, answers most every question, esp combined with Core Graphics
-
ArpitM about 6 yearsFor the repeat count, one can use
.infinity
. -
agrippa about 6 yearsworked very well for me (on a UIButton). Thank you!
-
Itachi over 5 yearsWeird! The target view couldn't receive any user tap gestures after calling this
rotateImageView
. Will it block the main runloop? -
Stoyan almost 5 yearsThe swift 4 version, stops the animation if app goes to background and back to foreground on the same screen
-
Max over 4 years@Itachi you have to pass an additional option to enable user interaction. developer.apple.com/documentation/uikit/…
-
Nicolas Manzini over 4 yearsi liked your simplicity
-
Nicolas Manzini over 4 yearsI like yours, pretty clean too
-
JCutting8 almost 3 yearsWhy was finding this so hard!?