Animated Progress Percentage Label Library

Finally, I made it. I have been thinking about creating animated progress percentage counter library an open source. Pardon me if name if confusing. I was thinking about the name for quite a while and then zeroed in on the above name.

This animated progress percentage counter is live and available on GitHub. It has cocoapods support too, so you can directly plug it into your iOS project.

I am more happy about it since this was all written in Swift which is neat, clean and enforces more disciplined guidelines for clean code.

You can find detailed documentation on GitHub page, but here I will write more about technical decisions I took and how I achieved some goals from developers' standpoint.

  • Instantiating JKProgressPercentageCounterView.

    There are two initializer that can be used to make progress percentage counter view.

    • Regular initializer
init(frame: CGRect, currentValue: Int, maximumValue: Int, titleDirection: TitleDirection, progressIndicatorHeight: CGFloat)
  • Convenience initializer
convenience init(currentValue: Int, maximumValue: Int)

The only difference between them is you don't have to specify all the parameters during initializer. Unspecified parameters get default values as follows.

 titleDirection = Top
 progressIndicatorHeight = 20
 frame = CGRectZero
  • Creation of backing and foreground view
    The progress indicator view has two main component. Background and foreground views.

      let progressIndicatorForegroundView = UIView()
        let progressIndicatorBackgroundView = UIView()
    

background view remains static and resizes according to the size of super view, while foreground view will stretch according to percentage completion value. (Calculated from currentValue and maximumValue passed to the initializer)

  • Setting up constraints

    We want backgroundView to remain static and change the width of foregroundView according to percentage completion value. This is done as follows.

    let fractionValue = Float(currentValue)/Float(maximumValue)
    

self.progressIndicatorForegroundViewWidthConstraint = NSLayoutConstraint(item: progressIndicatorForegroundView, attribute: .Width, relatedBy: .Equal, toItem: progressIndicatorBackgroundView, attribute: .Width, multiplier: CGFloat(fractionValue), constant: 1.0)
self.addConstraint(progressIndicatorForegroundViewWidthConstraint)

 This is true for static percentage view without any animation. However, when adding animation you have to continuously update the width of foreground view. Unfortunately Apple `NSLayoutConstraint` API does not allow us to update multiplier. So we will remove previous constraints and add new one with updated `multiplier`

 <pre class="line-numbers"><code class="language-swift">// Remove previous constraint.

self.removeConstraint(self.progressIndicatorForegroundViewWidthConstraint)
// Create new instance of constraint.
self.progressIndicatorForegroundViewWidthConstraint = NSLayoutConstraint(item: progressIndicatorForegroundView, attribute: .Width, relatedBy: .Equal, toItem: progressIndicatorBackgroundView, attribute: .Width, multiplier: CGFloat(fractionValue), constant: 1.0)
// Add the new constraint instance to view.
self.addConstraint(self.progressIndicatorForegroundViewWidthConstraint)

  • Creating animations
    • Progress indicator

      Animating progress indicator similar to animating any ordinary view constraints. First off we remove existing constraint on foregroundView width. call self.layoutIfNeeded() without animation block to update any remaining constraints. Add new constraint on the width of foregroundView and then again call self.layoutIfNeeded() in the animation block. Here is how it is done

      // Remove previous constraint.
      

self.removeConstraint(self.progressIndicatorForegroundViewWidthConstraint)
// Update the stale view layout.
self.layoutIfNeeded()
// Create new instance of foregroundWidthConstraint.
self.progressIndicatorForegroundViewWidthConstraint = NSLayoutConstraint(item: progressIndicatorForegroundView, attribute: .Width, relatedBy: .Equal, toItem: progressIndicatorBackgroundView, attribute: .Width, multiplier: CGFloat(fractionValue), constant: 1.0)
// Add new constraint.
self.addConstraint(self.progressIndicatorForegroundViewWidthConstraint)
// Call self.layoutIfNeeded inside animation block to animate updated constraint.
UIView.animateWithDuration(animationDuration, delay: 0.0, options: .CurveEaseInOut, animations: {
self.layoutIfNeeded()
}, completion: nil)

  • Label values

    Thanks to StackOverflow Answer on animating incrementing UILabel values, this part was easier to implement than I thought earlier. Here's is the full code shamelessly copy and pasted from above linked answer.
let delay = (animationDuration/NSTimeInterval(self.currentValue + 1)) * 1000000
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
            for i in 0...self.currentValue {
                usleep(UInt32(delay))
                dispatch_async(dispatch_get_main_queue(), {
                    let labelValue = self.fractionStringFromCurrentValue(i)
                    self.percentageCounterLabel.text = labelValue
                    if let labelIntValue = Int(labelValue) {
                        labelFormatterClosure?(labelIntValue, labelValue)
                    }
                });
            }            
        };

The final result of these animations is as follows,

animating_label_and_progress_indicator_progress

  • Passing formatting closure to client

    In order to facilitate user to change the formatting of progress label, a formatter block is provided to let user control what is shown in the label

    func showLabelWithDuration(animationDuration: NSTimeInterval, labelFormatterClosure: ((Int, String) -> ())?, completionClosure: (() -> Void)?)
    

// Values can be updated as follows.
<progress_percentage_view_instance>.showLabelWithDuration(2, labelFormatterClosure: {[weak <progress_percentage_view_instance>] labelValueInt, labelValueString in
<progress_percentage_view_instance>!.updatedLabelValue = "(labelValueString)%"
}, completionClosure: {
// Completion closure is used to notify client when animation is completed
print("Completed Animation")
})

Any feedback, critics, comments are most welcome. Let me know if you find any issues with this implementation or come up with ways to improve it

As mentioned before, This library is live and available on GitHub. It has cocoapods support too, so you can directly plug it into your iOS project.

Jayesh Kawli

I am a web and mobile developer working at Wayfair in Boston, MA. I come to learn so many things during course of life and I write about things which helped me and feel like they can help others too.

Subscribe to Fresh Beginning

Get the latest posts delivered right to your inbox.

or subscribe via RSS with Feedly!