skip to Main Content

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.

2

Answers


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

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

    Demo

    Login or Signup to reply.
  2. You can create a custom view where you can set all properties as per your requirement

    class CircularProgressBar: UIView {
        
        var progress: CGFloat = 0.2  {
            didSet {
                setNeedsDisplay()
            }
        }
        
        var progressColor: UIColor = .red {
            didSet {
                setNeedsDisplay()
            }
        }
        
        var nonProgressColor: UIColor = .lightGray {
            didSet {
                setNeedsDisplay()
            }
        }
        
        var lineWidth: CGFloat = 10 {
            didSet {
                setNeedsDisplay()
            }
        }
        
        var lineSpacing: CGFloat = 3 {
            didSet {
                setNeedsDisplay()
            }
        }
        
        var lineHeight: CGFloat = 15 {
            didSet {
                setNeedsDisplay()
            }
        }
        
        var centerCircleRadius: CGFloat = 5 {
            didSet {
                setNeedsDisplay()
            }
        }
        
        override func draw(_ rect: CGRect) {
            super.draw(rect)
            
            let center = CGPoint(x: rect.width / 2, y: rect.height / 2)
            let radius = min(rect.width, rect.height) / 2.5 - lineWidth
            
            // Draw track
            let trackPath = UIBezierPath(arcCenter: center, radius: radius, startAngle: -CGFloat.pi / 2, endAngle: 2 * CGFloat.pi, clockwise: true)
            nonProgressColor.setStroke()
            trackPath.lineWidth = lineWidth
            trackPath.stroke()
            
            // Draw lines
            let numLines = 60 // Adjust as needed
            let angleIncrement = CGFloat.pi * 2 / CGFloat(numLines)
            let lineStartRadius = radius + lineWidth / 2
            let lineEndRadius = lineStartRadius + lineSpacing + 12
            
            for i in 0..<numLines {
                let angle = CGFloat(i) * angleIncrement - CGFloat.pi / 2
                let startPoint = CGPoint(x: center.x + lineStartRadius * cos(angle), y: center.y + lineStartRadius * sin(angle))
                let endPoint = (CGFloat(i) / CGFloat(numLines) <= progress) ? CGPoint(x: center.x + lineEndRadius * cos(angle), y: center.y + (lineEndRadius + 5) * sin(angle)) : CGPoint(x: center.x + lineEndRadius * cos(angle), y: center.y + lineEndRadius * sin(angle))
                let linePath = UIBezierPath()
                linePath.move(to: startPoint)
                linePath.addLine(to: endPoint)
                linePath.lineWidth = lineWidth
                if CGFloat(i) / CGFloat(numLines) <= progress {
                    progressColor.setStroke()
                } else {
                    nonProgressColor.setStroke()
                }
                linePath.stroke()
            }
        }
    }
    

    Result : check here

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search