Rotate Animation in Swift

Updated on December 14, 2015 – Swift 2.0 + new examples

With this post, I intend to wrap up my series on animations as UIView Extensions in Swift… for now.  Truthfully, these ideas flowed out of a real-world app that I was working on, which required various simple animations (fading in/out, sliding text, and now, rotating a view 360 degrees).

Since I’ve given two other detailed walk-throughs on the topic, I’ll try to be to-the-point on this one.

As with the others, I’ve created a GitHub project for you to see the animation in action, and even modify to your liking.

The Extension

The following code adds a method to any UIView instance called rotate360Degrees. The code can be placed in a Swift file called “UIViewExtensions.swift”:

The only critical thing to notice in the above code snippet is the value passed to the CABasicAnimation constructor.  The "transform.rotation" string is what sets things up to go spinning, and the string must be typed exactly as-is for the animation to work.

As in my previous animation posts, I provide myself a couple of parameters to set for a little bit of customization if I want it. Since the parameters have default values, the method can be invoked by writing someUIViewInstance.rotate360Degrees() for simple cases.  For more “advanced” scenarios where you need to adjust how long the animation takes, or to perform some logic after the animation completes, you can pass in a duration value other than 1.0, assign a completionDelegate, or both, depending on your needs.

Check out the GitHub example for details on how to configure things for the completionDelegate.  I’ll be walking through that more “advanced” case shortly as well.

Example

Perhaps you’re asking, “Why spinning UIViews?”…

In my example, I’ve proposed a simple button that would be used to refresh the view / data in a real-world scenario.  When the button is tapped, I want the button to rotate 360 degrees.

In the “advanced” example, I want it to rotate continually until a process of some sort finishes, at which point the animation stops until initiated again.  Take a look:

Rotate Animation Example

 

Simple Case – Rotate Once

Once the UIView extension is in place, the simple use case is… well… pretty simple:

“Advanced” Case – Rotate Until Process Finishes

In my example, I decided to simulate a long-running process by using a custom-built Timer class, heavily inspired by Samuel Mullen’s implementation (with a few modifications to fit my needs).  If you’re looking through the GitHub example, try not to get too bogged down in the details of the Timer, unless it just intrigues you.  In real life, you may decide perform a web service call to refresh your data model, or refresh your UI (or both).  Whatever the case may be, you’ll likely end up with similar logic:

  • Refresh button is tapped
  • If the button isn’t already rotating, make it start
  • Kick off a process that may take some time
  • The animationDidStop callback is going to be invoked after the view has spun a full 360 degrees.  If the longish-running process is finished, the button can stop spinning.  Otherwise, it needs to spin around another time.  This will be repeated until the longish-running process is complete.

Confession:  I’m not entirely thrilled with the rampant mutability in my implementation, but I couldn’t figure out a way to do what I wanted in an immutable way.  It does work, however.  Just be aware that if you’re really a stickler for immutability in your classes, you’re going to hate this implementation (and I’d love to hear constructive feedback on how I could improve it!).  Here’s the code for the bullet-pointed process just outlined:

Summary

I tried to strike a balance between making these simple animations easy to call on my labels, buttons, and other UIView subclasses, and just shoving everything into a UIViewExtensions.swift file.  The related set of animations just seemed like a really nice use case for Swift extensions, and the strategy served me well in a recent project.  Hopefully the series has sparked some ideas in your mind for how to employ extensions to enhance the capabilities of a class so that your code is easier and cleaner to write.

As always – thanks for reading!

  • Pingback: Slide In Animation in Swift | Andrew Bancroft()

  • Pingback: Fade In / Out Animations as Class Extensions in Swift | Andrew Bancroft()

  • Pingback: The 5 W's of Swift Extensions - Andrew Bancroft()

  • Muhammad Rusdi

    nice

  • Thanks for this article. Much simpler to just write the extension. I made some adaption on the Timer:

    class ViewController: UIViewController {
    @IBOutlet weak var refreshButton: UIButton!
    var timeForProcess: CFTimeInterval = 3
    override func viewDidLoad() {
    super.viewDidLoad()
    }
    @IBAction func refresh(sender: AnyObject) {
    var timer = NSTimer.scheduledTimerWithTimeInterval(3, target: self, selector: Selector(“rotate”), userInfo: nil, repeats: false)
    }
    func rotate() {
    refreshButton.rotateByDegree(duration: timeForProcess, degree: 300){ myView in
    println (“this is a callback. do anything here (myView.bounds)”)
    }
    }
    }

  • Hi Andrew, I think this is what I’m after. I read through your post on the blog, and I “think” I get how it works, but I don’t get how to make the animation continue until shut off by an action or event. From what I can tell, the sample project just stops after 5 seconds?

    • Andrew Bancroft

      Hey Jed!

      The “Advanced Case” where the UIView instance rotates until some process finishes is the parallel to what you’re looking for I think. But there may very well be some different nuances to how you need to set things up in your own code.

      For me, I set up the timer to take a completion handler (a closure) which would be called and executed after the timer itself finished ticking all its ticks. I’ve got a couple of variables up at the top that get toggled to help indicate if the rotation animation needs to continue, or if it needs to stop (isRotating and shouldStopRotating).

      In your own case, you may end up structuring it similarly, but instead of toggling things based on the NSTimer, you may be able to detect some *other* way to say “Hey, this thing should stop rotating now”. Maybe your buttons toggle that shouldStopRotating variable, for example.

      The call to rotate360Degrees(_:) will simply rotate the UIView a full 360 degrees one time. If you need it to keep going, you’d simply keep calling it. The natural place to check that “should this keep rotating” condition is in the rotate360Degrees CAAnimation’s completionDelegate function called animationDidStop(_:finished:). If it shouldn’t stop rotating, call rotate360Degrees again.

      Is that semi-sort of helpful?

      • I’m a bit of an IOS newb so I get it, but not entirely. What I’ve found is that anything I try other than the timer doesn’t work. I tried setting the timer to 1 second and then starting the timer when my webview loaded. The kind of worked, but then I had other unrelated problems, such as how to create and remove the view every time a new page is loaded in the webview. Essentially, unrelated problems. Really, I just want a fancy loading animation for a webView instead of using the activity indicator. If you have any suggested resources I would love to hear about them.

        • Andrew Bancroft

          No worries, Jed! I updated the GitHub example project (https://github.com/andrewcbancroft/SwiftRotateAnimation) to include a new example where the “refresh icon” keep spinning continuously after it’s started until you click stop. It’s very similar to the second example using NSTimer, but it makes it a little more explicit as to when the “shouldStopSpinning” variable gets toggled.

          For a UIWebView, you probably want to wire up the delegate such that when webViewDidFinishLoad: gets called, shouldStopSpinning gets set to true. Check out https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIWebViewDelegate_Protocol/ for more info on the protocol. Don’t forget to specify up at the top of your view controller that it conforms to UIWebViewDelegate, and that you set the webView isntance’s (probably an IBOutlet in your view controller source file) delegate property appropriately so that the method actually does get called.