Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Circular progress view with outside stroke

Tags:

ios

uikit

swift

I am trying to create a circular progress view that has stroke outside. But my stroke starts from inside of the view instead of the start from the outside it like border. How can I solve it?

My code:

var progressLayer = CAShapeLayer()
var trackLayer = CAShapeLayer()

var progressLineWidth: CGFloat = 20
var progressColor = UIColor.green
var progress: Float = 0.5

let circlePath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2.0 - 2, y: frame.size.height / 2.0 - 2), radius: (frame.size.width) / 2, startAngle: -(.pi / 2), endAngle: .pi * 1.5, clockwise: true)

trackLayer.path = circlePath.cgPath
trackLayer.strokeColor = UIColor.gray.cgColor
trackLayer.lineWidth = 10
trackLayer.strokeEnd = 1.0
trackLayer.lineDashPattern = [4,4]
trackLayer.fillColor = UIColor.red.cgColor
layer.addSublayer(trackLayer)

progressLayer.path = circlePath.cgPath
progressLayer.fillColor = UIColor.clear.cgColor
progressLayer.strokeColor = progressColor.cgColor
progressLayer.lineWidth = progressLineWidth
progressLayer.strokeEnd = CGFloat(progress)
progressLayer.strokeStart = 0
progressLayer.lineDashPattern = [4,4]
layer.addSublayer(progressLayer)

Result:

enter image description here

What I want to achive:

enter image description here

Gray and green strokes should start from the outside of the red circle.

like image 528
Tolgay Toklar Avatar asked Oct 21 '25 11:10

Tolgay Toklar


1 Answers

Demo

You can subtract the middle circle by a simple mask like:

let maskLayer = CAShapeLayer()
maskLayer.path = UIBezierPath(rect: self.bounds).cgPath.subtracting(circlePath.cgPath)
layer.mask = maskLayer

Full working code: - (updated to support iOS below 16)

class CircularProgressBarView: UIView {
    var progress: CGFloat = 0 { didSet { layoutSubviews() } }
    var progressLineWidth: CGFloat = 20 { didSet { layoutSubviews() } }
    var progressColor = UIColor.green { didSet { layoutSubviews() } }
    var pattern: [NSNumber] = [4,4] { didSet { layoutSubviews() } }

    private var customLayer = CALayer()

    override func layoutSubviews() {
        super.layoutSubviews()

        customLayer.removeFromSuperlayer()
        customLayer = CALayer()

        let bounds = CGRect(
            x: progressLineWidth,
            y: progressLineWidth,
            width: bounds.width - progressLineWidth*2,
            height: bounds.height - progressLineWidth*2
        )

        let circlePath = UIBezierPath(ovalIn: bounds)

        let trackLayer = CAShapeLayer()
        trackLayer.path = circlePath.cgPath
        trackLayer.strokeColor = UIColor.gray.cgColor
        trackLayer.lineWidth = progressLineWidth
        trackLayer.lineDashPattern = pattern
        trackLayer.fillColor = UIColor.red.cgColor
        customLayer.addSublayer(trackLayer)

        let progressLayer = CAShapeLayer()
        progressLayer.path = circlePath.cgPath
        progressLayer.fillColor = UIColor.clear.cgColor
        progressLayer.strokeColor = progressColor.cgColor
        progressLayer.lineWidth = progressLineWidth * 2
        progressLayer.strokeEnd = progress
        progressLayer.lineDashPattern = pattern
        customLayer.addSublayer(progressLayer)

        let containerPath = UIBezierPath(rect: self.bounds)
        containerPath.append(circlePath.reversing())
        let maskLayer = CAShapeLayer()
        maskLayer.path = containerPath.cgPath
        customLayer.mask = maskLayer

        layer.addSublayer(customLayer)
    }
}

Of course you can rotate the whole layer (or view) to make it start from 12 O'clock

like image 142
Mojtaba Hosseini Avatar answered Oct 22 '25 23:10

Mojtaba Hosseini