Make CAGradientLayer fill the entire CAShapeLayer

32 Views Asked by At

I try to add an CAGradientLayer on top of a CAShapeLayer, but I can't get the full frame of the ShapeLayer without messing up the position of the GradientLayer, so the edges seems clipped. See photos below.

With GradientLayer:

enter image description here

Without GradientLayer:

enter image description here

This is the code:

class CircularProgressBarView: UIView {

// MARK: - Properties
var progressLayer = CAShapeLayer()
private var startPoint = CGFloat(-Double.pi / 2)
private var endPoint = CGFloat(3 * Double.pi / 2)
private var currentValue: Float = 0
var gradientLayer = CAGradientLayer()

override init(frame: CGRect) {
    super.init(frame: frame)
}

required init?(coder: NSCoder) {
    super.init(coder: coder)
}

override func layoutSubviews() {
    super.layoutSubviews()
    createCircularPath()
    createGradientPath()
}

func createCircularPath() {
    let circularPath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2.0, y: frame.size.height / 2.0), radius: frame.size.width / 2, startAngle: startPoint, endAngle: endPoint, clockwise: true)
    
    progressLayer.path = circularPath.cgPath
    progressLayer.fillColor = UIColor.clear.cgColor
    progressLayer.lineCap = .round
    progressLayer.lineWidth = 8
    progressLayer.strokeEnd = 0
    progressLayer.strokeColor = UIColor.white.cgColor
    
    layer.addSublayer(progressLayer)
}

func createGradientPath() {
    gradientLayer.type = CAGradientLayerType.conic
    gradientLayer.startPoint = CGPoint(x: 0.5, y: 0.5)
    gradientLayer.endPoint = CGPoint(x: 0.5, y: 0)

    gradientLayer.colors = [UIColor.yellow.cgColor, UIColor.red.cgColor]
    gradientLayer.frame = self.bounds
    gradientLayer.mask = progressLayer
    
    layer.addSublayer(gradientLayer)
}

}
1

There are 1 best solutions below

0
Duncan C On

At a guess, you need to inset your gradient layer's frame by your line width. So useself.bounds.insetBy(dx: lineWidth, dy: lineWidth) as your gradient view's frame.

That should add lineWidth/2 space on all 4 edges of your gradient layer.

Edit:

Sorry, you need to inset your Circular path by lineWidth. Try editing it like this:

func createCircularPath() {
    let rect = bounds.insetBy(dx: lineWidth/2, dy: lineWidth/2)
    let circularPath = UIBezierPath(arcCenter: CGPoint(x: rect.midX, y: rect.midY), radius: rect.size.width / 2, startAngle: startPoint, endAngle: endPoint, clockwise: true)
    
    progressLayer.path = circularPath.cgPath
    progressLayer.fillColor = UIColor.clear.cgColor
    progressLayer.lineCap = .round
    progressLayer.lineWidth = lineWidth
    progressLayer.strokeEnd = Double.pi * 2
    progressLayer.strokeColor = UIColor.blue.cgColor
    
    layer.addSublayer(progressLayer)
}

(Where I added an instance var var lineWidth = 8.0)

That produces this image on my simulator. (I made the parent view controller's view cyan so you can see what's going on with the CircularProgressBarView.)

enter image description here