How to make a rounded or filled and rounded progress view swift in swift (with CAShapeLayers)

10.5k views Asked by At

A few days ago, I wanted to create a rounded progress bar in swift

I watched many Videos and googled a lot.

In the most of the times the code did not work to it was too complex to understand. After some days I got it.

I thought, there must be some easier way and so created this code here.

It will look like this:

The code will be really easy to use.

enter image description here enter image description here

1

There are 1 answers

2
Michael On BEST ANSWER

It was not simple for me to find out how to make a progress bar rounded.

So I created a reusable code, so everybody can use it simple. I created a class.

Here is the class:

import UIKit

class CircularProgressView: UIView {


fileprivate var progressLayer = CAShapeLayer()
fileprivate var trackLayer = CAShapeLayer()
fileprivate var didConfigureLabel = false
fileprivate var rounded: Bool
fileprivate var filled: Bool


fileprivate let lineWidth: CGFloat?



var timeToFill = 3.43



var progressColor = UIColor.white {
    didSet{
        progressLayer.strokeColor = progressColor.cgColor
    }
}

var trackColor = UIColor.white {
    didSet{
        trackLayer.strokeColor = trackColor.cgColor
    }
}


var progress: Float {
    didSet{
        var pathMoved = progress - oldValue
        if pathMoved < 0{
            pathMoved = 0 - pathMoved
        }
        
        setProgress(duration: timeToFill * Double(pathMoved), to: progress)
    }
}




fileprivate func createProgressView(){
    
    self.backgroundColor = .clear
    self.layer.cornerRadius = frame.size.width / 2
    let circularPath = UIBezierPath(arcCenter: center, radius: frame.width / 2, startAngle: CGFloat(-0.5 * .pi), endAngle: CGFloat(1.5 * .pi), clockwise: true)
    trackLayer.fillColor = UIColor.blue.cgColor
    
    
    trackLayer.path = circularPath.cgPath
    trackLayer.fillColor = .none
    trackLayer.strokeColor = trackColor.cgColor
    if filled {
        trackLayer.lineCap = .butt
        trackLayer.lineWidth = frame.width
    }else{
        trackLayer.lineWidth = lineWidth!
    }
    trackLayer.strokeEnd = 1
    layer.addSublayer(trackLayer)
    
    progressLayer.path = circularPath.cgPath
    progressLayer.fillColor = .none
    progressLayer.strokeColor = progressColor.cgColor
    if filled {
        progressLayer.lineCap = .butt
        progressLayer.lineWidth = frame.width
    }else{
        progressLayer.lineWidth = lineWidth!
    }
    progressLayer.strokeEnd = 0
    if rounded{
        progressLayer.lineCap = .round
    }
    
    
    layer.addSublayer(progressLayer)
    
}





func trackColorToProgressColor() -> Void{
    trackColor = progressColor
    trackColor = UIColor(red: progressColor.cgColor.components![0], green: progressColor.cgColor.components![1], blue: progressColor.cgColor.components![2], alpha: 0.2)
}



func setProgress(duration: TimeInterval = 3, to newProgress: Float) -> Void{
    let animation = CABasicAnimation(keyPath: "strokeEnd")
    animation.duration = duration
    
    animation.fromValue = progressLayer.strokeEnd
    animation.toValue = newProgress
    
    progressLayer.strokeEnd = CGFloat(newProgress)
    
    progressLayer.add(animation, forKey: "animationProgress")
    
}



override init(frame: CGRect){
    progress = 0
    rounded = true
    filled = false
    lineWidth = 15
    super.init(frame: frame)
    filled = false
    createProgressView()
}

required init?(coder: NSCoder) {
    progress = 0
    rounded = true
    filled = false
    lineWidth = 15
    super.init(coder: coder)
    createProgressView()
    
}


init(frame: CGRect, lineWidth: CGFloat?, rounded: Bool) {
    
    
    progress = 0
    
    if lineWidth == nil{
        self.filled = true
        self.rounded = false
    }else{
        if rounded{
            self.rounded = true
        }else{
            self.rounded = false
        }
        self.filled = false
    }
    self.lineWidth = lineWidth
    
    super.init(frame: frame)
    createProgressView()
    
}

}

And here is how to use:

Create a new progress view

let progressView = CircularProgressView(frame: CGRect(x: 0, y: 0, width: 100, height: 100), lineWidth: 15, rounded: false)

If you use nil for the line width, a filled circle will appear like the second picture above.

Set the progress- and the track color:

progressView.progressColor = .blue

progressView.trackColor = .lightGray

If you use progressView.trackColorToProgressColor() set a track color to the progress color, but with a less alpha.

Set the position of the progressView (example):

progressView.center = view.center

You can use this because the progressView is of type UIView.

Add the progressView as a subview of the ViewControllers view:

view.addSubview(progressView)

Set the progress of the progressView:

progressView.progress = 0.6

The progress will be animated. If you don't want to animate the progress you can set the progressView.timeToFill to 0. If you want a faster or lower animating you can use this method, too.

I hope this code was helpful.