1

I am having difficulty adding a colour gradient to my circular progress bar

enter image description here

My setup:

  1. Storyboard: ViewController with an empty view designated as the Class CircularProgressBar
  2. Custom class for the CircularProgressBar

Steps Taken:

  1. I am able to load circular progress bar
  2. In earlier attempts I created a gradient shape layer & set it to be masked by the bounds of the BarLayer, however the gradient shape layer draw a rectangle starting from the centre of my circle & extend to the bottom right so only that portion of the BarLayer would have a gradient
  3. When I moved the origin of the gradient layer rectangle it shifted the Barlayer off the TrackLayer with it

How do I:

Have the gradient layer cover the entire circular progress bar & mask it to the BarLayer

class CircularProgressBar: UIView {

let shapeLayer = CAShapeLayer()
let trackLayer = CAShapeLayer()

override init(frame: CGRect) {
super.init(frame: frame)
    addProgressBar(radius: 5, progress: 0)
}

required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
    addProgressBar(radius: 5, progress: 0)
}

func addProgressBar(radius: CGFloat, progress: CGFloat) {

    let lineWidth = radius*0.080

    let circularPath = UIBezierPath(arcCenter: CGPoint(x: bounds.midX, y: bounds.midY), radius: radius, startAngle: 0, endAngle: CGFloat.pi*2, clockwise: true)

    //TrackLayer
    trackLayer.path = circularPath.cgPath
    trackLayer.fillColor = UIColor.lightGray.cgColor
    trackLayer.strokeColor = UIColor.clear.cgColor
    trackLayer.opacity = 0.5
    trackLayer.lineWidth = lineWidth
    trackLayer.lineCap = CAShapeLayerLineCap.round

    //BarLayer
    shapeLayer.path = circularPath.cgPath
    shapeLayer.fillColor = UIColor.clear.cgColor
    shapeLayer.strokeColor = UIColor.systemGreen.cgColor
    shapeLayer.lineWidth = lineWidth
    shapeLayer.strokeEnd = 0
    shapeLayer.lineCap = CAShapeLayerLineCap.round



    //Rotate Shape Layer
    shapeLayer.transform = CATransform3DMakeRotation(-CGFloat.pi/2, 0, 0, 1)

//Shape Shadow
    shapeLayer.shadowColor = UIColor.black.cgColor
    shapeLayer.shadowOffset = CGSize(width: -7, height: 7)
    shapeLayer.shadowRadius = 1
    shapeLayer.shadowOpacity = 0.5

    //Animation
    loadProgress(percentage: progress)

    //LoadLayers
    layer.addSublayer(trackLayer)
    layer.addSublayer(shapeLayer)

}  
func loadProgress(percentage: CGFloat) {

    let basicAnimation = CABasicAnimation(keyPath: "strokeEnd")
    basicAnimation.fromValue = 0
    basicAnimation.duration = 2
    basicAnimation.fillMode = CAMediaTimingFillMode.forwards
    basicAnimation.isRemovedOnCompletion = false
    shapeLayer.strokeEnd = percentage
    shapeLayer.add(basicAnimation, forKey: "basicStroke")

}

ViewController

class HomeViewController: UIViewController {

@IBOutlet weak var containerView: UIView!


var circularProgressBar = CircularProgressBar()
var radius: CGFloat!
var progress: CGFloat!

var answeredCorrect = 0
var totalQuestions = 0


override func viewDidLoad() {
    super.viewDidLoad()

    answeredCorrect = 50
    totalQuestions = 100

    //Configure Progress Bar
    radius = (containerView.frame.height)/2.60
    progress = CGFloat(answeredCorrect) / CGFloat (totalQuestions)
    circularProgressBar.addProgressBar(radius: radius, progress: progress)
    circularProgressBar.center = containerView.center

    //Adding view
    containerView.addSubview(circularProgressBar)


}  
}
Jawad Ali
  • 13,556
  • 3
  • 32
  • 49
Captain725
  • 73
  • 1
  • 7

1 Answers1

2

If you are looking for something like this

enter image description here

You need to update your code

import UIKit
@IBDesignable
class CircularProgressBar: UIView {

let shapeLayer = CAShapeLayer()
let trackLayer = CAShapeLayer()

override init(frame: CGRect) {
super.init(frame: frame)
   // addProgressBar(radius: 50, progress: 50)
}

required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
   // addProgressBar(radius: 50, progress: 50)
}
  override func layoutSubviews() {
    addProgressBar(radius: 50, progress: 50)
  }
func addProgressBar(radius: CGFloat, progress: CGFloat) {

  let lineWidth = CGFloat(10.0)//radius*0.080

    let circularPath = UIBezierPath(arcCenter: CGPoint(x: bounds.midX, y: bounds.midY), radius: radius, startAngle: 0, endAngle: CGFloat.pi*2, clockwise: true)

    //TrackLayer
    trackLayer.path = circularPath.cgPath
    trackLayer.fillColor = UIColor.lightGray.cgColor
    trackLayer.strokeColor = UIColor.clear.cgColor
    trackLayer.lineWidth = lineWidth
    trackLayer.lineCap = CAShapeLayerLineCap.round

    //BarLayer
    shapeLayer.path = circularPath.cgPath
    shapeLayer.fillColor = UIColor.black.cgColor
    shapeLayer.strokeColor = UIColor.systemGreen.cgColor
    shapeLayer.lineWidth = lineWidth*2
    //shapeLayer.strokeEnd = 0
    shapeLayer.lineCap = CAShapeLayerLineCap.round

    //Rotate Shape Layer
   // shapeLayer.transform = CATransform3DMakeRotation(-CGFloat.pi/2, 0, 0, 1)

    //Animation
   // loadProgress(percentage: progress)

    //LoadLayers
   layer.addSublayer(trackLayer)
   self.addGradient()

  // layer.addSublayer(shapeLayer)

}

  private func addGradient() {
    let gradient = CAGradientLayer()
    gradient.colors = [UIColor.red.cgColor,UIColor.purple.cgColor,UIColor.systemPink.cgColor,UIColor.blue.cgColor]
    gradient.frame = bounds
    gradient.mask = shapeLayer
    layer.addSublayer(gradient)
  }
}

And for Uniform gradient you can replace addGradient method

enter image description here

private func addGradient() {
    let gradient = CAGradientLayer()
    gradient.colors = [UIColor.red.cgColor,UIColor.cyan.cgColor,UIColor.brown.cgColor,UIColor.blue.cgColor]
    gradient.frame = bounds
    gradient.locations = [0.2,0.5,0.75,1]
    gradient.startPoint = CGPoint(x: 0.5, y: 0.5)
    gradient.endPoint = CGPoint(x: 0.5, y: 0)
    gradient.type = .conic
    gradient.mask = shapeLayer
    layer.addSublayer(gradient)
  }
Jawad Ali
  • 13,556
  • 3
  • 32
  • 49
  • Hey jawadAli, thank you again for your answers. I added a pictures of the progress bar (the gradient would be from dark green on the top to light green on the bottom) as well as some animation code for it to load as soon as the view controller appears. – Captain725 May 10 '20 at 11:24
  • Do I need to make any changes to my View Controller Code? – Captain725 May 10 '20 at 11:26
  • `loadProgress(percentage: progress)` update this method i questionn – Jawad Ali May 10 '20 at 11:27
  • Will do, added the animation code just under the addSublayers code in the first code box as a separate function – Captain725 May 10 '20 at 11:28
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/213549/discussion-between-jawadali-and-captain725). – Jawad Ali May 10 '20 at 11:29