Animate Time Count Down in Swift

Animate Time Count Down in Swift

We have just finished a piece in our UI for next project, and we are excited to share with you today. This is what it look like:

![Morph_CountDown](httMorph_CountDown

As you can see, the label display timestamp, which is suitable with duration field. And it count down to zero and animate the text at each tick. To achieve such animation, we use the awesome library TOMSMorphingLabel.

Setup Dependency

We use CocoaPods to manage dependencies, so we add the following line to our Podfile:

pod 'TOMSMorphingLabel', '~> 0.5'

Then run pod install against our working directory to have the library correctly installed.

Add Label to Storyboard

Drag an UILabel to your user interface, storyboard or .xib, then change its class to TOMSMorphingLabel:

Morph_CustomClass

Create an outlet for it inside our controller. This is where magic happen. In order to see our label transforming, simply set text to it:

self.durationLabel.text = "--:--"

Run the app and you should see the label is nicely transforming to newly set text.

Animate Time Count Down

To achieve the counting down, we need another variable to keep track the value of the count down. Make it class variable:

var remainingTrackDuration = 0

Next, we make a function, which will do:

  • Decrease value of count down variable by one
  • Properly format value and set new text to the label
  • Call itself again after 1 second

To call itself after a delay, we use dispatch_after macro and make the code to be called in main thread:

dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(1 * NSEC_PER_SEC)), dispatch_get_main_queue()) { () -> Void in
  // call the function itself here
}

Note the used parameters:

  • dispatch_time(): to create the delay between current time and delta value, which be one of NSEC_PER_SEC, or 1 second
  • dispatch_get_main_queue(): to run the code on main thread

We do the formatting using NSString formatter, note that if we has counted to zero, we simply do not call our function again:

self.remainingTrackDuration--
if (self.remainingTrackDuration >= 0) {
  self.durationLabel.text = NSString(format: "%ld:%.2ld", self.remainingTrackDuration / 60, self.remainingTrackDuration % 60) as String
}

So, our function should be like this:

func runTrackDurationAnimation() {
  dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(1 * NSEC_PER_SEC)), dispatch_get_main_queue()) { () -> Void in
    self.remainingTrackDuration--
    if (self.remainingTrackDuration >= 0) {
      self.durationLabel.text = NSString(format: "%ld:%.2ld", self.remainingTrackDuration / 60, self.remainingTrackDuration % 60) as String
      self.runTrackDurationAnimation()
    }
  }
}

Now we can set an initial value for count down variable, then call above function from anywhere and see the label transforming.

Cancel Scheduled Task

With Objective-C, in order to cancel such task, we use do not use dispatch and GCD, but prefer performSelector:withObject:afterDelay: and later, we cancel it with cancelPreviousPerformRequestsWithTarget:. However we do not have such methods in Swift, so to cancel a dispatched task, we may want to use a third-party library like Async.

With Async, our previous method should be like this, note that we need to store the created Async as a class variable so we can cancel it anytime:

var pendingAsync: Async?

func runTrackDurationAnimation() {
  self.pendingAsync?.cancel()
  self.pendingAsync = Async.main(after: 1) { () -> Void in
    self.remainingTrackDuration--
    if (self.remainingTrackDuration >= 0) {
      self.durationLabel.text = NSString(format: "%ld:%.2ld", self.remainingTrackDuration / 60, self.remainingTrackDuration % 60) as String
      self.runTrackDurationAnimation()
    }
  }
}

func cancelTrackDurationAnimation() {
  self.pendingAsync?.cancel()
}

Do not forget to import the module:

import Async

Conclusion

Minor animation and details made our apps more awesome, we are continuing to share with you about more like this piece in near future. Say tuned!