Circular progress view for the iOS apps

Circular progress view for the iOS apps

In this article, you will read about how to create the Circular progress view for the iOS apps (Swift) with animation.

Whether you need to show the percentage of the used amount or a fraction of the target number, one of way is to use the progress view. In our case, it will be a simple circular custom view.

In TripExBud we use the circular progress view quite often to represent the percentage form spent amount or available funds within budgets. Enough simple and powerful at the same time the circular view provides attractive visualization of a fraction of the amount or the percentage. Together with rounded corners and animation, it looks awesome.

Now let’s move on to the code

We’re going to programmatically create the custom view and call it from the controller. The circle inside of the view will match the size provided by the controller. Since the circle fits well into the square, so the best is to apply equally width and height.

Create the custom view

First, what we need it’s to create the custom view with initialization methods and the override function “draw”.

 

import UIKit

class CircularView: UIView{    

    override init(frame: CGRect){

        super.init(frame: frame)

    }

    required init?(coder: NSCoder) {

        super.init(coder: coder)

    }

        override func draw(_ rect: CGRect) {

    }

}

Our view contains a background view to represent the target. It’s defined as a gray color to highlight the whole circle and to draw frames for future circular view. Moreover, it’s a path for animation as well. The variables “lineWidth” and “lineColor” let’s leave as input parameters to adjust it depends on the screen requirements.


func drawBackRingFittingInsideView(lineWidth: CGFloat, lineColor: UIColor) {

        let halfSize:CGFloat = min( bounds.size.width/2, bounds.size.height/2)

        let desiredLineWidth:CGFloat = lineWidth

        let circle = CGFloat(Double.pi * 2)

        let startAngle = CGFloat(circle * 0.1)

        let endAngle = circle – startAngle

        let circlePath = UIBezierPath(

            arcCenter: CGPoint(x:halfSize, y:halfSize),

            radius: CGFloat( halfSize – (desiredLineWidth/2) ),

            startAngle: startAngle,

            endAngle: endAngle,

            clockwise: true)

        let shapeBackLayer = CAShapeLayer()

            shapeBackLayer.path = circlePath.cgPath

            shapeBackLayer.fillColor = UIColor.clear.cgColor

            shapeBackLayer.strokeColor = lineColor.cgColor

            shapeBackLayer.lineWidth = desiredLineWidth

            shapeBackLayer.lineCap = .round

        layer.addSublayer(shapeBackLayer)

    }

The circle is not closed, so we left 10% open from the whole circle diameter. We need this place to set up any long number going outside of our circle.

         let startAngle = CGFloat(circle * 0.1)

The UIBezierPath is a path that consists of straight and curved line segments that you can render in your custom views. More about UIBezierPath is here..

The CAShapeLayer is a layer that draws a cubic Bezier spline in its coordinate space. More here..

We need to create public variables to adjust them outside of the circular view.


var percent: Double = 0.0

var lineColor: UIColor?

var lineWidth = 3.0

var backgroundLineColor: UIColor?

var backgroundLineWidth = 3.0

let shapeLayer = CAShapeLayer()

The main view looks similar to the background view just with small changes for end angle. It’s a dynamic part and depends on the passed percent.


func drawRingFittingInsideView(lineWidth: CGFloat, lineColor: UIColor, percent: CGFloat) {

        let halfSize:CGFloat = min( bounds.size.width/2, bounds.size.height/2)

        let desiredLineWidth:CGFloat = lineWidth

        

        let circle = CGFloat(Double.pi * 2)

        let startAngle = CGFloat(circle * 0.1)

        let maxEndAngle = circle – (startAngle * 2)

        let endAngle = ((maxEndAngle * percent) / 100) + startAngle

        

        let circlePath = UIBezierPath(

            arcCenter: CGPoint(x:halfSize, y:halfSize),

            radius: CGFloat( halfSize – (desiredLineWidth/2) ),

            startAngle: startAngle,

            endAngle: endAngle,

            clockwise: true)

            shapeLayer.path = circlePath.cgPath

            shapeLayer.fillColor = UIColor.clear.cgColor

            shapeLayer.strokeColor = lineColor.cgColor

            shapeLayer.lineWidth = desiredLineWidth

            shapeLayer.lineCap = .round

        layer.addSublayer(shapeLayer)

    }

Both those methods we add to the “draw” function. In case if the percent is zero we still draw the background view and skip the main view. The math function “min” here to make the upper threshold and not to close the circle.

 

override func draw(_ rect: CGRect) {

          drawBackRingFittingInsideView(lineWidth: CGFloat(backgroundLineWidth),

lineColor: backgroundLineColor ?? .lightGray)

          if percent > 0 {

              percent = min(100, percent)

              drawRingFittingInsideView(lineWidth: CGFloat(lineWidth),

lineColor: lineColor ?? .blue, percent: CGFloat(percent))

          }

    }

The circular view with animation

The final step in the creation of the circular view is animation. The call and the duration of animation will be determined outside of the view.


func animateCircle(duration: TimeInterval) {

        let animation = CABasicAnimation(keyPath: “strokeEnd”)

        animation.duration = duration

        animation.fromValue = 0

        animation.toValue = 1

        animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)        

        shapeLayer.strokeEnd = 1.0

        shapeLayer.add(animation, forKey: “animateCircle”)

    }

The CABasicAnimation is an object that provides basic, single-keyframe animation capabilities for a layer property. More here..

Left only build and add programmatically the circular view in the controller view.

 

let circleView = CircleView(frame: CGRect(x: 0, y: 0, width: 108, height: 108))

      circleView.percent = 68.0

      circleView.lineColor = .blue

      circleView.backgroundLineWidth = 8.0

      circleView.lineWidth = 8.0

      circleView.animateCircle(duration: 0.8)

      self.addSubview(circleView)

The percent or the number inside the circular view can be added and managed as a uilabel outside in the controller view.

How it looks in the app TripExBud follow this link.

Leave a Comment

Your email address will not be published. Required fields are marked *