Search code examples
iosswiftobjective-cuikit

Circular progress bar in iOS with alpha long progress


There are some ways for creating a circular progress bar in iOS (Example).

enter image description here

But I'd like to achieve an effect where the head of the progressbar is rounded and the progress alpha fades along the way. Something like this:

enter image description here

Any ideas how can achieve this kind of effects?


Solution

  • One approach is to mask a CAGradientLayer with a CAShapeLayer. The shape layer should have an arc as its path, and a lineCap of .round.

    class FadingProgressView: UIView {
        let shapeLayer = CAShapeLayer()
        let gradientLayer = CAGradientLayer()
        
        override init(frame: CGRect) {
            super.init(frame: frame)
            backgroundColor = .clear
            layer.addSublayer(gradientLayer)
            gradientLayer.mask = shapeLayer
            gradientLayer.colors = [UIColor.blue.cgColor, UIColor.white.cgColor, UIColor.white.cgColor]
            gradientLayer.type = .conic
            gradientLayer.startPoint = CGPoint(x: 0.5, y: 0.5)
            gradientLayer.endPoint = CGPoint(x: 0.4, y: 0)
            gradientLayer.locations = [0, 0.75, 1]
            gradientLayer.backgroundColor = UIColor.clear.cgColor
            
            shapeLayer.fillColor = nil
            shapeLayer.strokeColor = CGColor(gray: 0, alpha: 1)
            shapeLayer.lineWidth = 5
            shapeLayer.lineCap = .round
            
            let animation = CABasicAnimation(keyPath: "transform.rotation")
            animation.fromValue = CGFloat.pi * 2
            animation.toValue = 0
            animation.duration = 1
            animation.repeatCount = .greatestFiniteMagnitude
            layer.add(animation, forKey: "transform.rotation")
        }
        
        override func layoutSubviews() {
            super.layoutSubviews()
            gradientLayer.frame = bounds
            shapeLayer.frame = bounds
            let path = UIBezierPath(
                arcCenter: .init(x: bounds.midX, y: bounds.midY),
                radius: bounds.width / 2 - 20,
                startAngle: 1.55 * .pi, endAngle: .pi, clockwise: true)
            shapeLayer.path = path.cgPath
        }
        
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    }
    

    Usage:

    class MyViewController: UIViewController {
        override func viewDidLoad() {
            let progress = FadingProgressView(frame: CGRect(x: 200, y: 200, width: 100, height: 100))
            view.addSubview(progress)
        }
    }