Search code examples
swiftgeometryuibezierpath

Swift Draw Custom Circle


I am trying to implement a custom circular analysis view. The view should be circular but cut-off.

Goal:

enter image description here

My Code:

let circlePath = UIBezierPath(ovalIn: CGRect(x: innerRect.minX, y: innerRect.minY, width: innerRect.width, height: innerRect.height))

if trackBackgroundColor != UIColor.clear {
    trackBackgroundColor.setFill()
    circlePath.fill();
}
if trackBorderWidth > 0 {
    circlePath.lineWidth = trackBorderWidth
    trackBorderColor.setStroke()
    circlePath.stroke()
}    

// progress Drawing
let progressPath = UIBezierPath()
let progressRect: CGRect = CGRect(x: innerRect.minX, y: innerRect.minY, width: innerRect.width, height: innerRect.height)
let center = CGPoint(x: progressRect.midX, y: progressRect.midY)
let radius = progressRect.width / 2.0

let startAngle:CGFloat = clockwise ? CGFloat(-internalProgress * Double.pi / 180.0) : CGFloat(constants.twoSeventyDegrees * Double.pi / 180)
let endAngle:CGFloat = clockwise ? CGFloat(constants.twoSeventyDegrees * Double.pi / 180) : CGFloat(-internalProgress * Double.pi / 180.0)

progressPath.addArc(withCenter: center, radius:radius, startAngle:startAngle, endAngle:endAngle, clockwise:!clockwise)

Current Output:

enter image description here

How do I draw the custom circle described as Goal.


Solution

  • Here's a rough implementation of what you need. This draws the two arcs.

    class CircularProgressView: UIView {
        var trackBackgroundColor = UIColor.lightGray
        var trackBorderWidth: CGFloat = 10
        var progressColor = UIColor.red
        var percent: Double = 0 {
            didSet {
                setNeedsDisplay()
            }
        }
    
        // Adjust these to meet your needs. 90 degrees is the bottom of the circle
        static let startDegrees: CGFloat = 120
        static let endDegrees: CGFloat = 60
    
        override func draw(_ rect: CGRect) {
            let startAngle: CGFloat = radians(of: CircularProgressView.startDegrees)
            let endAngle: CGFloat = radians(of: CircularProgressView.endDegrees)
            let progressAngle = radians(of: CircularProgressView.startDegrees + (360 - CircularProgressView.startDegrees + CircularProgressView.endDegrees) * CGFloat(max(0.0, min(percent, 1.0))))
    
            let center = CGPoint(x: bounds.midX, y: bounds.midY)
            let radius = min(center.x, center.y) - trackBorderWidth / 2 - 10
    
            print(startAngle, endAngle, progressAngle)
            let trackPath = UIBezierPath(arcCenter: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
            let progressPath = UIBezierPath(arcCenter: center, radius: radius, startAngle: startAngle, endAngle: progressAngle, clockwise: true)
            trackPath.lineWidth = trackBorderWidth
            trackPath.lineCapStyle = .round
            progressPath.lineWidth = trackBorderWidth
            progressPath.lineCapStyle = .round
    
            trackBackgroundColor.set()
            trackPath.stroke()
    
            progressColor.set()
            progressPath.stroke()
        }
    
        private func radians(of degrees: CGFloat) -> CGFloat {
            return degrees / 180 * .pi
        }
    }
    
    let progress = CircularProgressView(frame: CGRect(x: 0, y: 0, width: 500, height: 400))
    progress.backgroundColor = .white
    progress.percent = 0.95