Upvote:3

Aprove answer
class ViewController: UIViewController {

    private var progressView: GradientProgressView?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let progressView = GradientProgressView(frame: .init(x: 30.0, y: 30.0, width: 280.0, height: 350.0))
        progressView.backgroundColor = UIColor.lightGray // Just to debug
        progressView.transform = CGAffineTransform(rotationAngle: -.pi*0.5)
        progressView.points = {
            let count = 200
            let minimumRadius: CGFloat = 0.9
            let maximumRadius: CGFloat = 1.1
            
            return (0...count).map { index in
                let progress: CGFloat = CGFloat(index) / CGFloat(count)
                let angle = CGFloat.pi * 2.0 * progress
                let radius = CGFloat.random(in: minimumRadius...maximumRadius)
                return .init(x: cos(angle)*radius, y: sin(angle)*radius)
            }
        }()
        view.addSubview(progressView)
        self.progressView = progressView
        
        view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(animateProgress)))
    }
    
    @objc private func animateProgress() {
        let duration: TimeInterval = 1.0
        let startDate = Date()
        
        Timer.scheduledTimer(withTimeInterval: 1.0/60.0, repeats: true) { [weak self] timer in
            guard let self = self else {
                timer.invalidate()
                return
            }
            
            let progress = Date().timeIntervalSince(startDate)/duration
            
            if progress >= 1.0 {
                timer.invalidate()
            }
            self.progressView?.progress = max(0.0, min(CGFloat(progress), 1.0))
        }
    }


}

private extension ViewController {
    
    class GradientProgressView: UIView {
        
        var points: [CGPoint]? { didSet { setNeedsDisplay() } }
        var progress: CGFloat = 0.7 { didSet { setNeedsDisplay() } }
        
        override func draw(_ rect: CGRect) {
            super.draw(rect)
            
            let actualProgress = max(0.0, min(progress, 1.0))
            guard actualProgress > 0.0 else { return } // Nothing to draw
            
            let willClipAsProgress = actualProgress < 1.0
            
            guard let context = UIGraphicsGetCurrentContext() else { return }
            
            let lineWidth: CGFloat = 5.0
            guard let points = points else { return }
            guard let path = generatePath(withPoints: points, inFrame: bounds.insetBy(dx: lineWidth, dy: lineWidth)) else { return }
            
            if willClipAsProgress {
                context.saveGState()
                createProgressClippingPath().addClip()
            }
            
            
            drawGradient(path: path, context: context)
            drawLine(path: path, lineWidth: lineWidth, context: context)
            
            if willClipAsProgress {
                context.restoreGState()
            }
        }
        
        private func createProgressClippingPath() -> UIBezierPath {
            let endAngle = CGFloat.pi*2.0*progress
            
            let maxRadius: CGFloat = max(bounds.width, bounds.height) // we simply need one that is large enough.
            let path = UIBezierPath()
            let center: CGPoint = .init(x: bounds.midX, y: bounds.midY)
            path.move(to: center)
            path.addArc(withCenter: center, radius: maxRadius, startAngle: 0.0, endAngle: endAngle, clockwise: true)
            return path
        }
        
        private func drawGradient(path: UIBezierPath, context: CGContext) {
            context.saveGState()
            
            path.addClip() // This will be discarded once restoreGState() is called
            let gradient = CGGradient(colorsSpace: CGColorSpaceCreateDeviceRGB(), colors: [UIColor.blue, UIColor.green].map { $0.cgColor } as CFArray, locations: [0.0, 1.0])!
            context.drawRadialGradient(gradient, startCenter: CGPoint(x: bounds.midX, y: bounds.midY), startRadius: 0.0, endCenter: CGPoint(x: bounds.midX, y: bounds.midY), endRadius: min(bounds.width, bounds.height), options: [])
            
            context.restoreGState()
        }
        
        private func drawLine(path: UIBezierPath, lineWidth: CGFloat, context: CGContext) {
            UIColor.black.setStroke()
            path.lineWidth = lineWidth
            path.stroke()
        }
        
        
        
        private func generatePath(withPoints points: [CGPoint], inFrame frame: CGRect) -> UIBezierPath? {
            guard points.count > 2 else { return nil } // At least 3 points
            let pointsInPolarCoordinates: [(angle: CGFloat, radius: CGFloat)] = points.map { point in
                let radius = (point.x*point.x + point.y*point.y).squareRoot()
                let angle = atan2(point.y, point.x)
                return (angle, radius)
            }
            let maximumPointRadius: CGFloat = pointsInPolarCoordinates.max(by: { $1.radius > $0.radius })!.radius
            guard maximumPointRadius > 0.0 else { return nil } // Not all points may be centered
            
            let maximumFrameRadius = min(frame.width, frame.height)*0.5
            let radiusScale = maximumFrameRadius/maximumPointRadius
            
            let normalizedPoints: [CGPoint] = pointsInPolarCoordinates.map { polarPoint in
                .init(x: frame.midX + cos(polarPoint.angle)*polarPoint.radius*radiusScale,
                      y: frame.midY + sin(polarPoint.angle)*polarPoint.radius*radiusScale)
            }
            
            let path = UIBezierPath()
            path.move(to: normalizedPoints[0])
            normalizedPoints[1...].forEach { path.addLine(to: $0) }
            path.close()
            return path
        }
        
    }
    
}

Credit Goes to: stackoverflow.com

Related question with same questions but different answers