263

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.

Krunal
  • 77,632
  • 48
  • 245
  • 261
Derek
  • 9,773
  • 7
  • 29
  • 34

28 Answers28

337

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"];
}
Shaheen Ghiassy
  • 7,397
  • 3
  • 40
  • 40
Derek
  • 9,773
  • 7
  • 29
  • 34
  • 25
  • 3
    add QuartzCore.framework Project->Build Phases->Link Binaries – gjpc Oct 18 '12 at 22:46
  • 9
    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 – PaulWoodIII May 15 '13 at 04:46
  • @Antoine what `cumulative` actually means? – cvsguimaraes Jun 08 '13 at 21:37
  • 1
    @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." – smad Oct 16 '13 at 09:29
  • 1
    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. – Eliot Jan 28 '14 at 22:33
  • This works for me w/o cumulative. I set `toValue = @(2.0 * M_PI)` (no `fromValue`) – nielsbot Feb 05 '14 at 22:37
  • 1
    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. – Arjun Mehta Feb 28 '14 at 19:00
  • Could be a dumb question, is it not a better idea to do it in a block? Keep animation separate from main thread? – CalZone Aug 11 '14 at 21:11
  • 13
    This might help some one, **[view.layer removeAllAnimations];** to stop animation when needed. – arunit21 Aug 22 '14 at 13:57
  • Perfect. Worked better than the above answers. – mattsven Mar 03 '15 at 16:35
  • 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. – mattsven Jun 21 '15 at 00:18
  • I found it was spinning TOO FAST for my tastes so I added: rotationAnimation.speed = 0.06; - hope that helps someone! – tony.stack Aug 13 '15 at 15:03
  • @tony.stack I also want to change the speed. Thanks for your comment – Vivek Shah Sep 16 '15 at 06:52
  • @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.. – NGG Nov 16 '16 at 10:38
  • @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." – cvsguimaraes Nov 16 '16 at 20:28
  • Isn't this ... not infinite? – Iulian Onofrei Feb 02 '17 at 16:05
  • 1
    **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"]; }** – Harish J Mar 23 '17 at 09:10
114

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.

Iulian Onofrei
  • 9,188
  • 10
  • 67
  • 113
Nate
  • 31,017
  • 13
  • 83
  • 207
  • Thanks for the clear explanation and code. I had this same basic code, but your values made it fall into place. – Andrew Hoyer Aug 14 '12 at 07:20
  • 2
    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 Feb 28 '14 at 18:59
  • How to change spin direction in this code. nice code +1 – Dilip Manek Aug 12 '14 at 07:31
  • 1
    @Dilip, replace `M_PI / 2` with `- (M_PI / 2)`. (Scroll code to the right to see the end of each line) – Nate Aug 12 '14 at 07:41
  • 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. – Dilip Manek Oct 06 '14 at 11:09
  • 1
    @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. – Nate Oct 07 '14 at 05:58
  • 1
    What are you seeing happen, @MohitJethwa? I have an app that uses this, and it still works for me on iOS 8.1. – Nate Dec 06 '14 at 00:44
  • @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. – Nate Feb 02 '17 at 21:12
  • If the image rotated at 90 degrees is not the same as the image without rotation, the animation end-start jump will be noticed. – Iulian Onofrei Feb 02 '17 at 21:55
  • @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. – Nate Feb 03 '17 at 02:00
  • Is there a way to spin it 360 degrees horizontally/vertically? – Hemang Dec 28 '17 at 12:32
  • 1
    @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. – Nate Dec 29 '17 at 06:53
106

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)
    }
}
Kádi
  • 2,796
  • 1
  • 16
  • 22
  • What is the kRotationAnimationKey? – Luda Sep 28 '15 at 01:39
  • 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. – Kádi Sep 29 '15 at 07:10
  • Is it any string or something specific? – Luda Sep 29 '15 at 11:15
  • Sorry for late answer, but yes, it can be any string. – Kádi Oct 06 '15 at 10:04
  • 1
    // Any key as in the answer – Zander Zhang Feb 23 '17 at 07:55
  • @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? – ihsan Khan Aug 07 '17 at 18:30
  • @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 – Stephen J Jan 29 '18 at 19:14
  • The swift 4 version, stops the animation if app goes to background and back to foreground on the same screen – Stoyan Jun 07 '19 at 21:48
  • Why was finding this so hard!? – JCutting8 Jul 04 '21 at 08:49
69

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.

Farzad Karimi
  • 770
  • 1
  • 12
  • 31
ram
  • 921
  • 7
  • 8
  • 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. – Nate Jul 18 '13 at 22:37
  • Yes, I just thought I would explain why it is being used :) – ram Jul 18 '13 at 22:46
  • can be there stack overflow? – Nikita Nov 26 '13 at 16:01
  • 2
    @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. – huggie Dec 15 '13 at 10:15
  • 1
    Good answer +1 for a small version of code with desired functionality. – Pradeep Mittal Feb 13 '14 at 10:50
  • 1
    I really appreciated the explanation of why to not use UIViewAnimationOptionRepeat. – Jonathan Zhan Oct 05 '14 at 04:19
  • Weird! The target view couldn't receive any user tap gestures after calling this `rotateImageView`. Will it block the main runloop? – Itachi Oct 30 '18 at 08:37
  • @Itachi you have to pass an additional option to enable user interaction. https://developer.apple.com/documentation/uikit/uiviewanimationoptions?language=objc – Max Aug 30 '19 at 21:05
25

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")
    }
}
Kevin ABRIOUX
  • 16,507
  • 12
  • 93
  • 99
21

If all you want to do is rotate the image endlessly, this works quite well, and is very simple:

NSTimeInterval duration = 10.0f;
CGFloat angle = M_PI / 2.0f;
CGAffineTransform rotateTransform = CGAffineTransformRotate(imageView.transform, angle);

[UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionRepeat| UIViewAnimationOptionCurveLinear animations:^{
    imageView.transform = rotateTransform;
} completion:nil];

In my experience, this works flawlessly, but be sure your image is capable of being rotated around its center without any offsets, or the image animation will "jump" once it makes it around to PI.

To change the direction of the spin, change the sign of angle (angle *= -1).

Update Comments by @AlexPretzlav made me revisit this, and I realized that when I wrote this the image I was rotating was mirrored along both the vertical and horizontal axis, meaning the image was indeed only rotating 90 degrees and then resetting, though it looked like it was continuing to rotate all the way around.

So, if your image is like mine was, this will work great, however, if the image is not symmetrical, you'll notice the "snap" back to the original orientation after 90 degrees.

To rotate a non-symmetrical image, you're better off with the accepted answer.

One of these less elegant solutions, seen below, will truly rotate the image, but there may be a noticeable stutter when the animation is restarted:

- (void)spin
{
    NSTimeInterval duration = 0.5f;
    CGFloat angle = M_PI_2;
    CGAffineTransform rotateTransform = CGAffineTransformRotate(self.imageView.transform, angle);

    [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
        self.imageView.transform = rotateTransform;
    } completion:^(BOOL finished) {
        [self spin];
    }];
}

You could also do this just with blocks, as @richard-j-ross-iii suggests, but you will get a retain loop warning since the block is capturing itself:

__block void(^spin)() = ^{
    NSTimeInterval duration = 0.5f;
    CGFloat angle = M_PI_2;
    CGAffineTransform rotateTransform = CGAffineTransformRotate(self.imageView.transform, angle);

    [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveLinear animations:^{
        self.imageView.transform = rotateTransform;
    } completion:^(BOOL finished) {
        spin();
    }];
};
spin();
levigroker
  • 2,087
  • 1
  • 21
  • 30
21

David Rysanek's awesome answer updated to Swift 4:

import UIKit

extension UIView {

        func startRotating(duration: CFTimeInterval = 3, repeatCount: Float = Float.infinity, clockwise: Bool = true) {

            if self.layer.animation(forKey: "transform.rotation.z") != nil {
                return
            }

            let animation = CABasicAnimation(keyPath: "transform.rotation.z")
            let direction = clockwise ? 1.0 : -1.0
            animation.toValue = NSNumber(value: .pi * 2 * direction)
            animation.duration = duration
            animation.isCumulative = true
            animation.repeatCount = repeatCount
            self.layer.add(animation, forKey:"transform.rotation.z")
        }

        func stopRotating() {

            self.layer.removeAnimation(forKey: "transform.rotation.z")

        }   

    }
}
Geoff H
  • 3,107
  • 1
  • 28
  • 53
10

This was working for me:

[UIView animateWithDuration:1.0
                animations:^
{
    self.imageView.transform = CGAffineTransformMakeRotation(M_PI);
    self.imageView.transform = CGAffineTransformMakeRotation(0);
}];
P.J.Radadiya
  • 1,493
  • 1
  • 12
  • 21
inigo333
  • 3,088
  • 1
  • 36
  • 41
10

Here is my swift solution as a UIView extension. It could be considered as a simulation of a UIActivityIndicator behaviour on any UIImageView.

import UIKit

extension UIView
{

    /**
    Starts rotating the view around Z axis.

    @param duration Duration of one full 360 degrees rotation. One second is default.
    @param repeatCount How many times the spin should be done. If not provided, the view will spin forever.
    @param clockwise Direction of the rotation. Default is clockwise (true).
     */
    func startZRotation(duration duration: CFTimeInterval = 1, repeatCount: Float = Float.infinity, clockwise: Bool = true)
    {
        if self.layer.animationForKey("transform.rotation.z") != nil {
            return
        }
        let animation = CABasicAnimation(keyPath: "transform.rotation.z")
        let direction = clockwise ? 1.0 : -1.0
        animation.toValue = NSNumber(double: M_PI * 2 * direction)
        animation.duration = duration
        animation.cumulative = true
        animation.repeatCount = repeatCount
        self.layer.addAnimation(animation, forKey:"transform.rotation.z")
    }


    /// Stop rotating the view around Z axis.
    func stopZRotation()
    {
        self.layer.removeAnimationForKey("transform.rotation.z")
    }

}
P.J.Radadiya
  • 1,493
  • 1
  • 12
  • 21
David Rysanek
  • 901
  • 12
  • 16
9

I have found nice code in this repository,

Here is the code from it i have done small changes according to my need for speed :)

UIImageView+Rotate.h

#import <Foundation/Foundation.h>

@interface UIImageView (Rotate)
- (void)rotate360WithDuration:(CGFloat)duration repeatCount:(float)repeatCount;
- (void)pauseAnimations;
- (void)resumeAnimations;
- (void)stopAllAnimations;
@end

UIImageView+Rotate.m

#import <QuartzCore/QuartzCore.h>
#import "UIImageView+Rotate.h"

@implementation UIImageView (Rotate)

- (void)rotate360WithDuration:(CGFloat)duration repeatCount:(float)repeatCount
{

    CABasicAnimation *fullRotation;
    fullRotation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
    fullRotation.fromValue = [NSNumber numberWithFloat:0];
    //fullRotation.toValue = [NSNumber numberWithFloat:(2*M_PI)];
    fullRotation.toValue = [NSNumber numberWithFloat:-(2*M_PI)]; // added this minus sign as i want to rotate it to anticlockwise
    fullRotation.duration = duration;
    fullRotation.speed = 2.0f;              // Changed rotation speed
    if (repeatCount == 0)
        fullRotation.repeatCount = MAXFLOAT;
    else
        fullRotation.repeatCount = repeatCount;

    [self.layer addAnimation:fullRotation forKey:@"360"];
}

//Not using this methods :)

- (void)stopAllAnimations
{

    [self.layer removeAllAnimations];
};

- (void)pauseAnimations
{

    [self pauseLayer:self.layer];
}

- (void)resumeAnimations
{

    [self resumeLayer:self.layer];
}

- (void)pauseLayer:(CALayer *)layer
{

    CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
    layer.speed = 0.0;
    layer.timeOffset = pausedTime;
}

- (void)resumeLayer:(CALayer *)layer
{

    CFTimeInterval pausedTime = [layer timeOffset];
    layer.speed = 1.0;
    layer.timeOffset = 0.0;
    layer.beginTime = 0.0;
    CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
    layer.beginTime = timeSincePause;
}

@end
Dilip Manek
  • 9,095
  • 5
  • 44
  • 56
  • @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. – Dilip Manek May 15 '15 at 06:34
  • 1
    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. – BreadicalMD May 20 '15 at 18:46
9

A Swift3 version:

extension UIView {

    func startRotate() {
        let rotation : CABasicAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
        rotation.fromValue = 0
        rotation.toValue = NSNumber(value: M_PI * 2)
        rotation.duration = 2
        rotation.isCumulative = true
        rotation.repeatCount = FLT_MAX
        self.layer.add(rotation, forKey: "rotationAnimation")
    }

    func stopRotate() {
        self.layer.removeAnimation(forKey: "rotationAnimation")
    }
}

And remember to call startRotate in viewWillAppear not in viewDidLoad.

pableiros
  • 14,932
  • 12
  • 99
  • 105
Victor Choy
  • 4,006
  • 28
  • 35
9

Use quarter turn, and increase the turn incrementally.

void (^block)() = ^{
    imageToMove.transform = CGAffineTransformRotate(imageToMove.transform, M_PI / 2);
}

void (^completion)(BOOL) = ^(BOOL finished){
    [UIView animateWithDuration:1.0
                          delay:0.0
                        options:0
                     animations:block
                     completion:completion];
}

completion(YES);
Richard J. Ross III
  • 55,009
  • 24
  • 135
  • 201
  • 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. – Derek Mar 23 '12 at 19:24
8

@ram's answer was really helpful. Here's a Swift version of the answer.

Swift 2

private func rotateImageView() {

    UIView.animateWithDuration(1, delay: 0, options: UIViewAnimationOptions.CurveLinear, animations: { () -> Void in
        self.imageView.transform = CGAffineTransformRotate(self.imageView.transform, CGFloat(M_PI_2))
        }) { (finished) -> Void in
            if finished {
                self.rotateImageView()
            }
    }
}

Swift 3,4,5

private func rotateImageView() {

    UIView.animate(withDuration: 1, delay: 0, options: UIView.AnimationOptions.curveLinear, animations: { () -> Void in
        self.imageView.transform = self.imageView.transform.rotated(by: .pi / 2)
    }) { (finished) -> Void in
        if finished {
            self.rotateImageView()
        }
    }
}
Karen Hovhannisyan
  • 1,140
  • 2
  • 21
  • 31
yoninja
  • 1,952
  • 2
  • 31
  • 39
  • how can I stop rotation? – user2526811 Sep 18 '16 at 06:36
  • 2
    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() }` – yoninja Sep 21 '16 at 05:19
  • Thank you, I exactly did the same. Thanks for your response. – user2526811 Sep 21 '16 at 05:54
3

You can also do the same type of animation using UIView and blocks. Here is a class extension method which can rotate the view by any angle.

- (void)rotationWithDuration:(NSTimeInterval)duration angle:(CGFloat)angle options:(UIViewAnimationOptions)options
{
    // Repeat a quarter rotation as many times as needed to complete the full rotation
    CGFloat sign = angle > 0 ? 1 : -1;
    __block NSUInteger numberRepeats = floorf(fabsf(angle) / M_PI_2);
    CGFloat quarterDuration = duration * M_PI_2 / fabs(angle);

    CGFloat lastRotation = angle - sign * numberRepeats * M_PI_2;
    CGFloat lastDuration = duration - quarterDuration * numberRepeats;

    __block UIViewAnimationOptions startOptions = UIViewAnimationOptionBeginFromCurrentState;
    UIViewAnimationOptions endOptions = UIViewAnimationOptionBeginFromCurrentState;

    if (options & UIViewAnimationOptionCurveEaseIn || options == UIViewAnimationOptionCurveEaseInOut) {
        startOptions |= UIViewAnimationOptionCurveEaseIn;
    } else {
        startOptions |= UIViewAnimationOptionCurveLinear;
    }

    if (options & UIViewAnimationOptionCurveEaseOut || options == UIViewAnimationOptionCurveEaseInOut) {
        endOptions |= UIViewAnimationOptionCurveEaseOut;
    } else {
        endOptions |= UIViewAnimationOptionCurveLinear;
    }

    void (^lastRotationBlock)(void) = ^ {
        [UIView animateWithDuration:lastDuration 
                              delay:0 
                            options:endOptions 
                         animations:^{
                             self.transform = CGAffineTransformRotate(self.transform, lastRotation);
                         } 
                         completion:^(BOOL finished) {
                             NSLog(@"Animation completed");   
                         }
         ];
    };

    if (numberRepeats) {
        __block void (^quarterSpinningBlock)(void) = ^{ 
            [UIView animateWithDuration:quarterDuration 
                                  delay:0 
                                options:startOptions 
                             animations:^{
                                 self.transform = CGAffineTransformRotate(self.transform, M_PI_2);
                                 numberRepeats--; 
                             } 
                             completion:^(BOOL finished) {
                                 if (numberRepeats > 0) {
                                     startOptions = UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionCurveLinear;
                                     quarterSpinningBlock();
                                 } else {
                                     lastRotationBlock();
                                 }NSLog(@"Animation completed");   
                             }
             ];

        };

        quarterSpinningBlock();
    } else {
        lastRotationBlock();
    }
}
MiKL
  • 1,850
  • 1
  • 15
  • 23
  • 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 ? – alecail Jun 09 '13 at 19:20
3

If anyone wanted nates' solution but in swift, then here is a rough swift translation:

class SomeClass: UIViewController {

    var animating : Bool = false
    @IBOutlet weak var activityIndicatorImage: UIImageView!

    func startSpinning() {
        if(!animating) {
            animating = true;
            spinWithOptions(UIViewAnimationOptions.CurveEaseIn);
        }
    }

    func stopSpinning() {
        animating = false
    }

    func spinWithOptions(options: UIViewAnimationOptions) {
        UIView.animateWithDuration(0.5, delay: 0.0, options: options, animations: { () -> Void in
            let val : CGFloat = CGFloat((M_PI / Double(2.0)));
            self.activityIndicatorImage.transform = CGAffineTransformRotate(self.activityIndicatorImage.transform,val)
        }) { (finished: Bool) -> Void in

            if(finished) {
                if(self.animating){
                    self.spinWithOptions(UIViewAnimationOptions.CurveLinear)
                } else if (options != UIViewAnimationOptions.CurveEaseOut) {
                    self.spinWithOptions(UIViewAnimationOptions.CurveEaseOut)
                }
            }

        }
    }

    override func viewDidLoad() {
        startSpinning()
    }
}
Adrian_H
  • 1,548
  • 1
  • 14
  • 27
3

for xamarin ios:

public static void RotateAnimation (this UIView view, float duration=1, float rotations=1, float repeat=int.MaxValue)
{
    var rotationAnimation = CABasicAnimation.FromKeyPath ("transform.rotation.z");
    rotationAnimation.To = new NSNumber (Math.PI * 2.0 /* full rotation*/ * 1 * 1);
    rotationAnimation.Duration = 1;
    rotationAnimation.Cumulative = true;
    rotationAnimation.RepeatCount = int.MaxValue;
    rotationAnimation.RemovedOnCompletion = false;
    view.Layer.AddAnimation (rotationAnimation, "rotationAnimation");
}
Danil Shaykhutdinov
  • 2,027
  • 21
  • 26
2

This is how I rotate 360 in right direction.

[UIView animateWithDuration:1.0f delay:0.0f options:UIViewAnimationOptionRepeat|UIViewAnimationOptionCurveLinear
                     animations:^{
                         [imageIndView setTransform:CGAffineTransformRotate([imageIndView transform], M_PI-0.00001f)];
                     } completion:nil];
Pavel
  • 21
  • 2
2

Create the animation

- (CABasicAnimation *)spinAnimationWithDuration:(CGFloat)duration clockwise:(BOOL)clockwise repeat:(BOOL)repeats
{
    CABasicAnimation *anim = [CABasicAnimation animationWithKeyPath:@"transform.rotation.z"];
    anim.toValue = clockwise ? @(M_PI * 2.0) : @(M_PI * -2.0);
    anim.duration = duration;
    anim.cumulative = YES;
    anim.repeatCount = repeats ? CGFLOAT_MAX : 0;
    return anim;
}

Add it to a view like this

CABasicAnimation *animation = [self spinAnimationWithDuration:1.0 clockwise:YES repeat:YES];
[self.spinningView.layer addAnimation:animation forKey:@"rotationAnimation"];

How is this answer different? You will have way cleaner code if most of your functions returns objects instead of just manipulating some objects here and there.

hfossli
  • 22,616
  • 10
  • 116
  • 130
2

There are following different ways to perform 360 degree animation with UIView.

Using CABasicAnimation

var rotationAnimation = CABasicAnimation()
rotationAnimation = CABasicAnimation.init(keyPath: "transform.rotation.z")
rotationAnimation.toValue = NSNumber(value: (Double.pi))
rotationAnimation.duration = 1.0
rotationAnimation.isCumulative = true
rotationAnimation.repeatCount = 100.0
view.layer.add(rotationAnimation, forKey: "rotationAnimation")


Here is an extension functions for UIView that handles start & stop rotation operations:

extension UIView {

    // Start rotation
    func startRotation() {
        let rotation = CABasicAnimation(keyPath: "transform.rotation.z")
        rotation.fromValue = 0
        rotation.toValue = NSNumber(value: Double.pi)
        rotation.duration = 1.0
        rotation.isCumulative = true
        rotation.repeatCount = FLT_MAX
        self.layer.add(rotation, forKey: "rotationAnimation")
    }

    // Stop rotation
    func stopRotation() {
        self.layer.removeAnimation(forKey: "rotationAnimation")
    }
}


Now using, UIView.animation closure:

UIView.animate(withDuration: 0.5, animations: { 
      view.transform = CGAffineTransform(rotationAngle: (CGFloat(Double.pi)) 
}) { (isAnimationComplete) in
    // Animation completed 
}
Krunal
  • 77,632
  • 48
  • 245
  • 261
2

Swift 5 UIView Extension using Keyframe Animations

This approach allows us to directly use the UIView.AnimationOptions.repeat

public extension UIView {

    func animateRotation(duration: TimeInterval, repeat: Bool, completion: ((Bool) -> ())?) {

        var options = UIView.KeyframeAnimationOptions(rawValue: UIView.AnimationOptions.curveLinear.rawValue)

        if `repeat` {
            options.insert(.repeat)
        }

        UIView.animateKeyframes(withDuration: duration, delay: 0, options: options, animations: {

            UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.25, animations: {
                self.transform = CGAffineTransform(rotationAngle: CGFloat.pi/2)
            })

            UIView.addKeyframe(withRelativeStartTime: 0.25, relativeDuration: 0.25, animations: {
                self.transform = CGAffineTransform(rotationAngle: CGFloat.pi)
            })

            UIView.addKeyframe(withRelativeStartTime: 0.5, relativeDuration: 0.25, animations: {
                self.transform = CGAffineTransform(rotationAngle: 3*CGFloat.pi/2)
            })

            UIView.addKeyframe(withRelativeStartTime: 0.75, relativeDuration: 0.25, animations: {
                self.transform = CGAffineTransform(rotationAngle: 2*CGFloat.pi)
            })

        }, completion: completion)

    }

}
Brody Robertson
  • 8,506
  • 2
  • 47
  • 42
1

I has developed a shiny animation framework which can save you tone of time! Using it this animation can be created very easily:

private var endlessRotater: EndlessAnimator!
override func viewDidAppear(animated: Bool) 
{
    super.viewDidAppear(animated)
    let rotationAnimation = AdditiveRotateAnimator(M_PI).to(targetView).duration(2.0).baseAnimation(.CurveLinear)
    endlessRotater = EndlessAnimator(rotationAnimation)
    endlessRotater.animate()
}

to stop this animation simply set nil to endlessRotater.

If you are interested, please take a look: https://github.com/hip4yes/Animatics

P.J.Radadiya
  • 1,493
  • 1
  • 12
  • 21
Nikita Arkhipov
  • 1,208
  • 4
  • 11
  • 28
1

Swift 4,

func rotateImage(image: UIImageView) {
        UIView.animate(withDuration: 1, animations: {
            image.transform = CGAffineTransform(rotationAngle: CGFloat.pi)
            image.transform = CGAffineTransform.identity
        }) { (completed) in
            self.rotateImage()
        }
    }

Ref

Mohammad Zaid Pathan
  • 16,304
  • 7
  • 99
  • 130
1

Swift 4.0

func rotateImageView()
{
    UIView.animate(withDuration: 0.3, delay: 0, options: .curveLinear, animations: {() -> Void in
        self.imageView.transform = self.imageView.transform.rotated(by: .pi / 2)
    }, completion: {(_ finished: Bool) -> Void in
        if finished {
            rotateImageView()
        }
    })
}
Abhishek Jain
  • 4,557
  • 2
  • 32
  • 31
0

Swift :

func runSpinAnimationOnView(view:UIView , duration:Float, rotations:Double, repeatt:Float ) ->()
    {
        let rotationAnimation=CABasicAnimation();

        rotationAnimation.keyPath="transform.rotation.z"

        let toValue = M_PI * 2.0 * rotations ;


        // passing it a float
        let someInterval = CFTimeInterval(duration)

        rotationAnimation.toValue=toValue;
        rotationAnimation.duration=someInterval;
        rotationAnimation.cumulative=true;
        rotationAnimation.repeatCount=repeatt;
        view.layer.addAnimation(rotationAnimation, forKey: "rotationAnimation")


    }
Erhan Demirci
  • 4,173
  • 4
  • 36
  • 44
0

Swift 3 :

 var rotationAnimation = CABasicAnimation()
     rotationAnimation = CABasicAnimation.init(keyPath: "transform.rotation.z")
     rotationAnimation.toValue = NSNumber(value: (M_PI * 2.0))
     rotationAnimation.duration = 2.0
     rotationAnimation.isCumulative = true
     rotationAnimation.repeatCount = 10.0
     view.layer.add(rotationAnimation, forKey: "rotationAnimation")
Deepak Carpenter
  • 1,504
  • 2
  • 10
  • 21
0
let val = CGFloat(M_PI_2)

UIView.animate(withDuration: 1, delay: 0, options: [.repeat, .curveLinear], animations: {
        self.viewToRotate.transform = self.viewToRotate.transform.rotated(by: val)
})
Warif Akhand Rishi
  • 23,920
  • 8
  • 80
  • 107
0
import UIKit

class RotatingImageView: UIImageView, CAAnimationDelegate {
    
    private let rotationAnimationKey = "rotationAnimationKey"

    private var shouldStopRotating = false
    
    func startRotating(witFullRotationDuration duration: Double = 0.5, halfRotation: Bool = true) {
        
        shouldStopRotating = false
        if layer.animation(forKey: rotationAnimationKey) == nil {
        
            let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation")
            rotationAnimation.fromValue = 0.0
            rotationAnimation.toValue = halfRotation ? Float.pi : Float.pi * 2
            rotationAnimation.duration = duration
            rotationAnimation.repeatCount = 1
            rotationAnimation.delegate = self
            layer.add(rotationAnimation, forKey: rotationAnimationKey)
        
        }
        
    }

    func stopRotating(immediately: Bool = false) {
        if immediately {
            if layer.animation(forKey: rotationAnimationKey) != nil {
                layer.removeAnimation(forKey: rotationAnimationKey)
            }
        } else {
            shouldStopRotating = true
        }
    }
    
    func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
    
        if !shouldStopRotating {
            startRotating(witFullRotationDuration: anim.duration)
        } else {
            if layer.animation(forKey: rotationAnimationKey) != nil {
                layer.removeAnimation(forKey: rotationAnimationKey)
            }
        }
        
    }
    
}
Peter Lapisu
  • 19,915
  • 16
  • 123
  • 179
-1

I think you should better add a UIVIew Category:

#import <QuartzCore/QuartzCore.h>
#import "UIView+Rotate.h"

Implementation UIView (Rotate)

  • (void)remrotate360WithDuration:(CGFloat)duration repeatCount: (float)repeatCount
    {
        CABasicAnimation *fullRotation;
        fullRotation = [CABasicAnimation animationWithKeyPath:@"transform.rotation"];
        fullRotation.fromValue = [NSNumber numberWithFloat:0];
        fullRotation.toValue = [NSNumber numberWithFloat:(2*M_PI)];
        // fullRotation.toValue = [NSNumber numberWithFloat:-(2*M_PI)]; // added this minus sign as i want to rotate it to anticlockwise
        fullRotation.duration = duration;
        fullRotation.speed = 2.0f; // Changed rotation speed
        if (repeatCount == 0)
            fullRotation.repeatCount = MAXFLOAT;
        else
            fullRotation.repeatCount = repeatCount;
    
        [self.layer addAnimation:fullRotation forKey:@"360"];
    }
    

Not using this methods :)

  • (void)remstopAllAnimations
    {
        [self.layer removeAllAnimations];
    };
    
  • (void)rempauseAnimations
    {
        [self rempauseLayer:self.layer];
    }
    
  • (void)remresumeAnimations
    {
        [self remresumeLayer:self.layer];
    }
    
  • (void)rempauseLayer:(CALayer *)layer
    {
        CFTimeInterval pausedTime = [layer convertTime:CACurrentMediaTime() fromLayer:nil];
        layer.speed = 0.0;
        layer.timeOffset = pausedTime;
    }
    
  • (void)remresumeLayer:(CALayer *)layer
    {
        CFTimeInterval pausedTime = [layer timeOffset];
        layer.speed = 1.0;
        layer.timeOffset = 0.0;
        layer.beginTime = 0.0;
        CFTimeInterval timeSincePause = [layer convertTime:CACurrentMediaTime() fromLayer:nil] - pausedTime;
        layer.beginTime = timeSincePause;
    }
    
Lukas Körfer
  • 13,515
  • 7
  • 46
  • 62
Zgpeace
  • 3,927
  • 33
  • 31