33

I want to rotate an image view for 360 degrees indefinitely.

UIView.animate(withDuration: 2, delay: 0, options: [.repeat], animations: {
    self.view.transform = CGAffineTransform(rotationAngle: 6.28318530717959)
}, completion: nil)

How can I do it?

Cesare
  • 9,139
  • 16
  • 78
  • 130

11 Answers11

44

UPDATE Swift 5.x

// duration will helps to control rotation speed
 private func rotateView(targetView: UIView, duration: Double = 5) {
    UIView.animate(withDuration: duration, delay: 0.0, options: .curveLinear, animations: {
        targetView.transform = targetView.transform.rotated(by: .pi)
    }) { finished in
        self.rotateView(targetView: targetView, duration: duration)
    }
}

Swift 2.x way to rotate UIView indefinitely, compiled from earlier answers:

// Rotate <targetView> indefinitely
private func rotateView(targetView: UIView, duration: Double = 1.0) {
    UIView.animateWithDuration(duration, delay: 0.0, options: .CurveLinear, animations: {
        targetView.transform = CGAffineTransformRotate(targetView.transform, CGFloat(M_PI))
    }) { finished in
        self.rotateView(targetView, duration: duration)
    }
}

UPDATE Swift 3.x

// Rotate <targetView> indefinitely
private func rotateView(targetView: UIView, duration: Double = 1.0) {
    UIView.animate(withDuration: duration, delay: 0.0, options: .curveLinear, animations: {
        targetView.transform = targetView.transform.rotated(by: CGFloat(M_PI))
    }) { finished in
        self.rotateView(targetView: targetView, duration: duration)
    }
}
Sunil Targe
  • 7,251
  • 5
  • 49
  • 80
Crashalot
  • 33,605
  • 61
  • 269
  • 439
  • 3
    This is not a smooth animation. There is a slight hiccup every time the animation repeats. – David James Nov 30 '16 at 16:52
  • It stop first animation and just after animation completely stop it is started again (( How to forced it move around without latency on between animation – Sirop4ik Dec 24 '16 at 18:42
  • @AlekseyTimoshchenko, in order to get the animation to start again, you must reset its transforms back to the identity in the `finished` block `targetView.transform = .identity` – Joshua Lieberman Aug 25 '18 at 01:13
32

Swift 3.0

let imgViewRing = UIImageView(image: UIImage(named: "apple"))
imgViewRing.frame = CGRect(x: 0, y: 0, width: UIImage(named: "apple")!.size.width, height: UIImage(named: "apple")!.size.height)
imgViewRing.center = CGPoint(x: self.view.frame.size.width/2.0, y: self.view.frame.size.height/2.0)
rotateAnimation(imageView: imgViewRing)
self.view.addSubview(imgViewRing)

This is the animation logic

func rotateAnimation(imageView:UIImageView,duration: CFTimeInterval = 2.0) {
        let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
        rotateAnimation.fromValue = 0.0
        rotateAnimation.toValue = CGFloat(.pi * 2.0)
        rotateAnimation.duration = duration
        rotateAnimation.repeatCount = .greatestFiniteMagnitude

        imageView.layer.add(rotateAnimation, forKey: nil)
    }

You can check output in this link

Hardik Thakkar
  • 15,269
  • 2
  • 94
  • 81
  • 3
    Instead of **M_PI**, you can use **CGFloat.pi**, I think it looks a bit more ***swifty*** – Mr. Xcoder Mar 21 '17 at 12:59
  • Hi @Mr.Xcoder your code is working fine for me, but if i am trying to apply this animation in collectionview cell animation is not working properly. some cells animating some cell not. – Krishna Kumar Thakur Mar 15 '18 at 13:00
7

Use this extension to rotate UIImageView 360 degrees.

extension UIView {
func rotate360Degrees(duration: CFTimeInterval = 1.0, completionDelegate: AnyObject? = nil) {
    let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
    rotateAnimation.fromValue = 0.0
    rotateAnimation.toValue = CGFloat(M_PI)
    rotateAnimation.duration = duration

    if let delegate: CAAnimationDelegate = completionDelegate as! CAAnimationDelegate? {
        rotateAnimation.delegate = delegate
    }
    self.layer.addAnimation(rotateAnimation, forKey: nil)
}
}

Than to rotate UIImageView simply use this method

self.YOUR_SUBVIEW.rotate360Degrees()
Saqib Omer
  • 5,387
  • 7
  • 50
  • 71
  • Are you able to include this extension for **NSImageView** with swift? – Steve Apr 17 '16 at 06:58
  • 3
    In swift 3: there is a problem: Cannot assign value of type 'AnyObject' to type 'CAAnimationDelegate?' to Fix it: Switch this `if let delegate: AnyObject = completionDelegate` by this `if let delegate: CAAnimationDelegate = completionDelegate as! CAAnimationDelegate?` – Hassan Taleb Sep 14 '16 at 07:48
  • 1
    Thank you @HassanTaleb, that was what I was looking for... stupid Swift3 u_u broke all my app! – kodartcha Sep 19 '16 at 07:40
  • 1
    You need also need to change `rotateAnimation.toValue = CGFloat(M_PI)` to `rotateAnimation.toValue = CGFloat(M_PI * 2)` to make it move around – Sirop4ik Dec 24 '16 at 19:29
6

This one work for me in Swift 2.2:

let rotationAnimation = CABasicAnimation(keyPath: "transform.rotation.z")
rotationAnimation.fromValue = 0.0
rotationAnimation.toValue = 360 * CGFloat(M_PI/180)
let innerAnimationDuration : CGFloat = 1.0
rotationAnimation.duration = Double(innerAnimationDuration)
rotationAnimation.repeatCount = HUGE
self.imageView.addAnimation(rotationAnimation, forKey: "rotateInner")
saroj raut
  • 834
  • 8
  • 16
5

I would stick it in a function like rotateImage() and in the completion code just call rotateImage() again. I think you should use M_PI (or the swift equivalent) for the rotation amount, though.

nanothread
  • 908
  • 3
  • 10
  • 25
  • 1
    M_PI is 180 degrees. But rotating by 2 * M_PI will actually not do anything. But yes, you are correct. Rotate 180 degrees in a method and then call that same method again in the completion. – Fogmeister Jan 03 '15 at 13:56
  • 4
    You don't want the repeat option if you are calling a completion routine. Replace `UIViewAnimationOptions.Repeat` with `UIViewAnimationOptions.CurveLinear` – vacawama Jan 03 '15 at 14:16
3

Updated for Swift 3: I made an extension to UIView and included the following function within:

func rotate(fromValue: CGFloat, toValue: CGFloat, duration: CFTimeInterval = 1.0, completionDelegate: Any? = nil) {

let rotateAnimation = CABasicAnimation(keyPath: >"transform.rotation")
rotateAnimation.fromValue = fromValue
rotateAnimation.toValue = toValue
rotateAnimation.duration = duration

if let delegate: Any = completionDelegate {
rotateAnimation.delegate = delegate as? CAAnimationDelegate
}
self.layer.add(rotateAnimation, forKey: nil)

}

You can then call the function via (on a UIView I made into a button for example) :

monitorButton.rotate(fromValue: 0.0, toValue: CGFloat(M_PI * 2), completionDelegate: self)

Hope this helps!

Randoramma
  • 123
  • 9
2

I think what you really want here is to use a CADisplayLink. Reason being that this would be indefinitely smooth versus using completion blocks which may cause slight hiccups and are not as easily cancelable. See the following solution:

var displayLink : CADisplayLink?
var targetView = UIView()

func beginRotation () {

    // Setup display link
    self.displayLink = CADisplayLink(target: self, selector: #selector(onFrameInterval(displayLink:)))
    self.displayLink?.preferredFramesPerSecond = 60
    self.displayLink?.add(to: .current, forMode: RunLoop.Mode.default)
}

func stopRotation () {

    // Invalidate display link
    self.displayLink?.invalidate()
    self.displayLink = nil
}

// Called everytime the display is refreshed
@objc func onFrameInterval (displayLink: CADisplayLink) {

    // Get frames per second
    let framesPerSecond = Double(displayLink.preferredFramesPerSecond)

    // Based on fps, calculate how much target view should spin each interval
    let rotationsPerSecond = Double(3)
    let anglePerSecond = rotationsPerSecond * (2 * Double.pi)
    let anglePerInterval = CGFloat(anglePerSecond / framesPerSecond)

    // Rotate target view to match the current angle of the interval
    self.targetView.layer.transform = CATransform3DRotate(self.targetView.layer.transform, anglePerInterval, 0, 0, 1)

}
Garret Kaye
  • 2,412
  • 4
  • 21
  • 45
2

Avoiding the completion closure with recursive calls!

Bit late to this party, but using UIView keyFrame animation & varying stages of rotation for each keyFrame, plus setting the animation curve works nicely. Here's an UIView class function -

class func rotate360(_ view: UIView, duration: TimeInterval, repeating: Bool = true) {

    let transform1 = CGAffineTransform(rotationAngle: .pi * 0.75)
    let transform2 = CGAffineTransform(rotationAngle: .pi * 1.5)

    let animationOptions: UInt
    if repeating {
        animationOptions = UIView.AnimationOptions.curveLinear.rawValue | UIView.AnimationOptions.repeat.rawValue
    } else {
        animationOptions = UIView.AnimationOptions.curveLinear.rawValue
    }

    let keyFrameAnimationOptions = UIView.KeyframeAnimationOptions(rawValue: animationOptions)

    UIView.animateKeyframes(withDuration: duration, delay: 0, options: [keyFrameAnimationOptions, .calculationModeLinear], animations: {
        UIView.addKeyframe(withRelativeStartTime: 0, relativeDuration: 0.375) {
            view.transform = transform1
        }
        UIView.addKeyframe(withRelativeStartTime: 0.375, relativeDuration: 0.375) {
            view.transform = transform2
        }
        UIView.addKeyframe(withRelativeStartTime: 0.75, relativeDuration: 0.25) {
            view.transform = .identity
        }
    }, completion: nil)
}

Looks pretty gnarly, with the weird rotation angles, but as the op & others have found, you can't just tell it to rotate 360

SomaMan
  • 4,127
  • 1
  • 34
  • 45
1

Try this one it works for me, i am rotating image for once

 UIView.animateWithDuration(0.5, delay: 0.0, options: .CurveLinear, animations: {

       self.imgViewReload.transform = CGAffineTransformRotate(self.imgViewReload.transform, CGFloat(M_PI))

 }, completion: {
    (value: Bool) in

          UIView.animateWithDuration(0.5, delay: 0.0, options: .CurveLinear, animations: {
          self.imgViewReload.transform = CGAffineTransformIdentity

         }, completion: nil)
 })
Patel Jigar
  • 2,141
  • 1
  • 23
  • 30
1

you can use this function ... just give it the view and the duration

it has two animations the the first rotates the view 180° and the other one rotates it to 360° then the function calls itself which allows it to continue the rotation animation infinitely

func infinite360Animation(targetView: UIView, duration: Double) {
    UIView.animate(withDuration: duration/2, delay: 0, options: .curveLinear) {
        targetView.transform = CGAffineTransform.identity.rotated(by: .pi )
    } completion: { (_) in
        UIView.animate(withDuration: duration/2, delay: 0, options: .curveLinear) {
            targetView.transform = CGAffineTransform.identity.rotated(by: .pi * 2)
        } completion: { (_) in
            self.infinite360Animation(targetView: targetView, duration: duration)
        }
    }
}

You can then call the function like this

infinite360Animatio(targetView: yourView, duration: 3)
Azzam Rar
  • 85
  • 8
0

On your tabBarController, make sure to set your delegate and do the following didSelect method below:

func tabBarController(_ tabBarController: UITabBarController,
                          didSelect viewController: UIViewController) {
        if let selectedItem:UITabBarItem = tabBarController.tabBar.selectedItem {
            animated(tabBar: tabBarController.tabBar, selectedItem: selectedItem)
        }
    }




fileprivate func animated(tabBar: UITabBar, selectedItem:UITabBarItem){
        if let view:UIView = selectedItem.value(forKey: "view") as? UIView {
            if let currentImageView = view.subviews.first as? UIImageView {
                rotate(imageView: currentImageView, completion: { (completed) in
                    self.restore(imageView: currentImageView, completion: nil)
                })
            }
        }
    }

    fileprivate func rotate(imageView:UIImageView, completion:((Bool) ->Void)?){
        UIView.animate(withDuration: animationSpeed, delay: 0.0, options: .curveLinear, animations: {
            imageView.transform = CGAffineTransform.init(rotationAngle: CGFloat.pi)
        }, completion: {
            (value: Bool) in
            completion?(value)
        })
    }

    fileprivate func restore(imageView:UIImageView, completion:((Bool) ->Void)?){
        UIView.animate(withDuration: animationSpeed, delay: 0.0, options: .curveLinear, animations: {
            imageView.transform = CGAffineTransform.identity
        }, completion: {
            (value: Bool) in
            completion?(value)
        })
    }
LuAndre
  • 1,114
  • 12
  • 23