Animated Progress Percentage Label Library

I have been thinking about creating an animated progress percentage counter library open source and finally I have done it. In this blog post I am going to talk in more depth regarding technical details of this library.

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

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

Instantiating JKProgressPercentageCounterView.

There are two initializers 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 the 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 components. Background and foreground views.

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

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

Setting up constraints

We want background view to remain static and change the width of foreground view according to the percentage completion values. 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)

// Add Constraint
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 the foreground view. Unfortunately, Apple NSLayoutConstraint API does not allow us to update the multiplier. So we will remove previous constraints and add a new one with updated multiplier



// 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 

view.self.addConstraint(self.progressIndicatorForegroundViewWidthConstraint)

Creating animations

Progress indicator

Animating progress indicators similar to animating any ordinary view constraints. First off we remove the existing constraint on foreground view width. call self.layoutIfNeeded() without animation block to update any remaining constraints. Add a new constraint on the width of foreground view 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 completedprint("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.