25

I've been trying to run an animation that rotates my UIButton 360 degrees using this code:

UIView.animateWithDuration(3.0, animations: {
  self.vineTimeCapButton.transform = CGAffineTransformMakeRotation(CGFloat(M_PI*2))
  self.instagramTimeCapButton.transform = CGAffineTransformMakeRotation(CGFloat(M_PI*2))
})

However, it doesn't rotate 360 degrees because the UIButton is already at that location.

How can I rotate my UIButton 360 degrees?

Eric Aya
  • 69,473
  • 35
  • 181
  • 253
Nadal Alyafaie
  • 329
  • 1
  • 3
  • 12

8 Answers8

46

You can use a trick: start rotating with 180 degrees first and then rotate with 360 degrees. Use 2 animations with delay. Try this.

UIView.animate(withDuration: 0.5) {
    self.view.transform = CGAffineTransform(rotationAngle: .pi)
}

UIView.animate(
    withDuration: 0.5,
    delay: 0.45,
    options: UIView.AnimationOptions.curveEaseIn
) {
    self.view.transform = CGAffineTransform(rotationAngle: 2 * .pi)
}
aheze
  • 24,434
  • 8
  • 68
  • 125
truongky
  • 1,147
  • 9
  • 12
9

As discussed here, you can also use a CAAnimation. This code applies one full 360 rotation:

Swift 3

let fullRotation = CABasicAnimation(keyPath: "transform.rotation")
fullRotation.delegate = self
fullRotation.fromValue = NSNumber(floatLiteral: 0)
fullRotation.toValue = NSNumber(floatLiteral: Double(CGFloat.pi * 2))
fullRotation.duration = 0.5
fullRotation.repeatCount = 1
button.layer.add(fullRotation, forKey: "360")

You will need to import QuartzCore:

import QuartzCore

And your ViewController needs to conform to CAAnimationDelegate:

class ViewController: UIViewController, CAAnimationDelegate {

}
Kuldeep
  • 4,466
  • 8
  • 32
  • 59
Kqtr
  • 5,824
  • 3
  • 25
  • 32
8

Swift 4: Animation nested closure is better than animation delay block.

UIView.animate(withDuration: 0.5, animations: { 
  button.transform = CGAffineTransform(rotationAngle: (CGFloat(Double.pi))) 
 }) { (isAnimationComplete) in

       // Nested Block
UIView.animate(withDuration: 0.5) { 
   button.transform = CGAffineTransform(rotationAngle: (CGFloat(Double.pi * 2)))
       }    
}
Krunal
  • 77,632
  • 48
  • 245
  • 261
8

Swift 4 version that allows you to rotate the same view infinite times:

UIView.animate(withDuration: 0.5) {
  self.yourButton.transform = self.yourButton.transform.rotated(by: CGFloat.pi)
}

if you want to rotate 360°:

UIView.animate(withDuration: 0.5) {
  self.yourButton.transform = self.yourButton.transform.rotated(by: CGFloat.pi)
  self.yourButton.transform = self.yourButton.transform.rotated(by: CGFloat.pi)
}
budiDino
  • 13,044
  • 8
  • 95
  • 91
  • 1
    This isn't working as the end position is the same as the start position. – tobi Dec 07 '18 at 12:34
  • @tobi, thanks. I never tried rotating 360° so I didn't know it doesn't work. I've updated the answer to handle that scenario. – budiDino Dec 19 '18 at 11:03
  • For the 360 degree version, the second transform rotation needs to be 2 * pi. That is: self.yourButton.transform = self.yourButton.transform.rotated(by: CGFloat.pi * 2) – Reefwing Oct 23 '19 at 07:39
  • @Reefwing does your suggestion work? It has been a while since I wrote the code above but I think I had some kind of issue where the button wouldn't even animate if the rotation is `pi*2` (same position at the start and end of animation). If you can confirm the `pi*2` code works, I'll update my answer. Thank you! (maybe I'll have time to test it myself later today) – budiDino Oct 23 '19 at 08:17
  • @ budidino - it worked for me, although I was using an image view not a button. Note that you still need both statements like you have, but the second one needs to be pi*2. – Reefwing Oct 24 '19 at 00:07
  • 1
    @Reefwing I just tested and if I put `pi*2` it only rotates `180'` and the code I posted still does a `360'` for me. I'll leave it as it is for now but it's good that people can see your comment if they for some reason have the same issue as you. – budiDino Oct 24 '19 at 04:53
  • @ budidino - no worries. It could be because I initialise the transform in viewDidLoad. i.e. yourButton.transform = CGAffineTransform(rotationAngle: 0.0) – Reefwing Oct 24 '19 at 22:44
6

in Swift 3 :

UIView.animate(withDuration:0.5, animations: { () -> Void in
  button.transform = CGAffineTransform(rotationAngle: CGFloat(M_PI))
})

UIView.animate(withDuration: 0.5, delay: 0.45, options: .curveEaseIn, animations: { () -> Void in
  button.transform = CGAffineTransform(rotationAngle: CGFloat(M_PI * 2))
}, completion: nil)
budiDino
  • 13,044
  • 8
  • 95
  • 91
Hussein Dimessi
  • 377
  • 3
  • 8
5

You can also try to implement an extension which will simplify things if you need to do it multiple time

extension UIView {
    
    func rotate360Degrees(duration: CFTimeInterval = 1, repeatCount: Float = .infinity) {
        let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
        rotateAnimation.fromValue = 0.0
        rotateAnimation.toValue = CGFloat(Double.pi * 2)
        rotateAnimation.isRemovedOnCompletion = false
        rotateAnimation.duration = duration
        rotateAnimation.repeatCount = repeatCount
        layer.add(rotateAnimation, forKey: nil)
    }
    
    // Call this if using infinity animation
    func stopRotation () {
        layer.removeAllAnimations()
    }
}
Abhishek Maurya
  • 697
  • 7
  • 19
4

You can use a little extension based on CABasicAnimation (Swift 4.x):

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

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

Usage:

For example we can start to make a simple button:

let button = UIButton()
button.frame = CGRect(x: self.view.frame.size.width/2, y: 150, width: 50, height: 50)
button.backgroundColor = UIColor.red
button.setTitle("Name your Button ", for: .normal)
button.addTarget(self, action: #selector(buttonAction), for: .touchUpInside)
self.view.addSubview(button)

Then we can build its selector:

@objc func buttonAction(sender: UIButton!) {
        print("Button tapped")
        sender.rotate360Degrees()
}
Alessandro Ornano
  • 34,887
  • 11
  • 106
  • 133
  • The UIView extension can only be used for elements which derive from `UIView`. It can't be used for elements such as `UIBarButtonItem` – Prashant Oct 30 '18 at 07:46
  • @Prashant No, you wrong. Extension are not limited as you said.Extensions add new functionality to an existing class, structure, enumeration, or protocol type. This includes the ability to extend types for which you do not have access to the original source code (known as retroactive modeling). If you need more info about extension look [here](https://docs.swift.org/swift-book/LanguageGuide/Extensions.html) – Alessandro Ornano Oct 30 '18 at 07:56
  • what I'm trying to say is your code would have to be modified a bit to be used for a `UIBarButtonItem` . For all other UI elements that are derived from UIView, the above extension can be added. Here's how it would be for a `UIBarButtonItem` The line `self.layer.add(rotateAnimation, forKey: nil)` will be modified as `self.customView!.layer.add(rotateAnimation, forKey: nil)` – Prashant Nov 01 '18 at 05:57
  • Oh sure, that's correct, you've reason. I've update my old answer based on a old modified question (that's because we spoken about UIBarButtonItem..), now it works. – Alessandro Ornano Nov 01 '18 at 11:27
-2

Swift 5. Try this, it worked for me

UIView.animate(withDuration: 3) {
  self.yourButton.transform = CGAffineTransform(rotationAngle: .pi)
  self.yourButton.transform = CGAffineTransform(rotationAngle: .pi * 2)
 }