UIView Infinite 360 degree rotation animation?

169,643

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")
    }
}
Share:
169,643

Related videos on Youtube

Derek
Author by

Derek

Updated on August 26, 2022

Comments

  • Derek
    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 the UIView 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 to 0 degrees before it starts again.

  • Derek
    Derek about 12 years
    I 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
    AlBeebe almost 12 years
    #import <QuartzCore/QuartzCore.h>
  • Andrew Hoyer
    Andrew Hoyer over 11 years
    Thanks for the clear explanation and code. I had this same basic code, but your values made it fall into place.
  • gjpc
    gjpc over 11 years
    add QuartzCore.framework Project->Build Phases->Link Binaries
  • PaulWoodIII
    PaulWoodIII almost 11 years
    This 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
    kbtz almost 11 years
    @Antoine what cumulative actually means?
  • alecail
    alecail almost 11 years
    Would 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
    Nate almost 11 years
    The 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
    ram almost 11 years
    Yes, I just thought I would explain why it is being used :)
  • smad
    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
    Nikita over 10 years
    can be there stack overflow?
  • huggie
    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
    Eliot over 10 years
    Also in this method, changing the duration: parameter seems to have no effect since it affects both the actual rotationAnimation.duration and the toValue. I fixed this in my code by removing duration as a coefficient in the toValue calculation.
  • nielsbot
    nielsbot over 10 years
    This works for me w/o cumulative. I set toValue = @(2.0 * M_PI) (no fromValue)
  • Pradeep Mittal
    Pradeep Mittal about 10 years
    Good answer +1 for a small version of code with desired functionality.
  • Arjun Mehta
    Arjun Mehta about 10 years
    Using 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
    Arjun Mehta about 10 years
    I 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
    Maverick almost 10 years
    This simple and nice solution saved my day :)
  • CalZone
    CalZone almost 10 years
    Could be a dumb question, is it not a better idea to do it in a block? Keep animation separate from main thread?
  • Dilip
    Dilip almost 10 years
    How to change spin direction in this code. nice code +1
  • Nate
    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
    arunit21 over 9 years
    This might help some one, [view.layer removeAllAnimations]; to stop animation when needed.
  • Jonathan Zhan
    Jonathan Zhan over 9 years
    I really appreciated the explanation of why to not use UIViewAnimationOptionRepeat.
  • Dilip
    Dilip over 9 years
    Hi 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
    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 call stopSpin 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
    Alex Pretzlav over 9 years
    This only rotated 1/4 of a rotation for me.
  • levigroker
    levigroker over 9 years
    @AlexPretzlav Be sure you have UIViewAnimationOptionRepeat set in your animation options.
  • Alex Pretzlav
    Alex Pretzlav over 9 years
    I did, it rotates 45°, and then loops by jumping back to 0° and rotating 45° again
  • levigroker
    levigroker over 9 years
    Updated my answer with new information about why this "worked"
  • Nate
    Nate over 9 years
    What are you seeing happen, @MohitJethwa? I have an app that uses this, and it still works for me on iOS 8.1.
  • mattsven
    mattsven about 9 years
    Perfect. Worked better than the above answers.
  • Dilip
    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
    BreadicalMD almost 9 years
    I'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
    Dilip almost 9 years
    @BreadicalMD No worries, but its good suggestion, i have updated my code. Thanx.
  • mattsven
    mattsven almost 9 years
    This 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
    tony.stack over 8 years
    I found it was spinning TOO FAST for my tastes so I added: rotationAnimation.speed = 0.06; - hope that helps someone!
  • Vivek Shah
    Vivek Shah over 8 years
    @tony.stack I also want to change the speed. Thanks for your comment
  • Luda
    Luda over 8 years
    What is the kRotationAnimationKey?
  • Kádi
    Kádi over 8 years
    It 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
    Luda over 8 years
    Is it any string or something specific?
  • Kádi
    Kádi over 8 years
    Sorry for late answer, but yes, it can be any string.
  • byJeevan
    byJeevan about 8 years
    It is not happening for infinite times !
  • user2526811
    user2526811 over 7 years
    how can I stop rotation?
  • yoninja
    yoninja over 7 years
    How about you put a flag? So, maybe you have a func for stopping the animation: var stop = false private func stopRotation() { stop = true } Then, inside if finished {...}: if finished { if stop { return} self.rotateImageView() }
  • user2526811
    user2526811 over 7 years
    Thank you, I exactly did the same. Thanks for your response.
  • NGG
    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
    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
    Iulian Onofrei over 7 years
    Isn't this ... not infinite?
  • Nate
    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
    Iulian Onofrei over 7 years
    If the image rotated at 90 degrees is not the same as the image without rotation, the animation end-start jump will be noticed.
  • Nate
    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
    Zander Zhang about 7 years
    // Any key as in the answer
  • Harish J
    Harish J about 7 years
    rotationAnimation.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
    Zgpeace almost 7 years
    Well done. I just want to comment on the UIImageView category. Thank you all the same.
  • ihsan Khan
    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
    Hemang over 6 years
    Is there a way to spin it 360 degrees horizontally/vertically?
  • Nate
    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
    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
    ArpitM about 6 years
    For the repeat count, one can use .infinity.
  • agrippa
    agrippa about 6 years
    worked very well for me (on a UIButton). Thank you!
  • Itachi
    Itachi over 5 years
    Weird! The target view couldn't receive any user tap gestures after calling this rotateImageView. Will it block the main runloop?
  • Stoyan
    Stoyan almost 5 years
    The swift 4 version, stops the animation if app goes to background and back to foreground on the same screen
  • Max
    Max over 4 years
    @Itachi you have to pass an additional option to enable user interaction. developer.apple.com/documentation/uikit/…
  • Nicolas Manzini
    Nicolas Manzini over 4 years
    i liked your simplicity
  • Nicolas Manzini
    Nicolas Manzini over 4 years
    I like yours, pretty clean too
  • JCutting8
    JCutting8 almost 3 years
    Why was finding this so hard!?