Woodgun11 Woodgun11 - 1 month ago 24
iOS Question

CAShapeLayer with gradient

I have a problem with adding gradient to CAShapeLayer. I've created circle layer and I want to add a gradient to it. So, I created a CAGradientLayer and set it frames to layer bounds and mask to layer and it doesn't appear. What can I do?

private func setupCircle() -> CAShapeLayer {
let circlePath = UIBezierPath(arcCenter: CGPoint(x: progressView.frame.size.width / 2.0, y: progressView.frame.size.height / 2.0), radius: (progressView.frame.size.width - 10)/2, startAngle: 0.0, endAngle: CGFloat(M_PI * 2.0), clockwise: true)

let circleLayer = CAShapeLayer()
circleLayer.path = circlePath.CGPath
circleLayer.fillColor = nil
circleLayer.strokeColor = UIColor.yellowColor().CGColor
circleLayer.backgroundColor = UIColor.yellowColor().CGColor
circleLayer.lineWidth = configurator.lineWidth
circleLayer.strokeEnd = 1.0

return circleLayer
}

private func setupGradient() -> CAGradientLayer {
let gradient = CAGradientLayer()
gradient.colors = [UIColor.clearColor().CGColor, UIColor.yellowColor().CGColor, UIColor.greenColor().CGColor]
gradient.locations = [0.0, 0.25, 1.0]
gradient.startPoint = CGPointMake(0, 0)
gradient.endPoint = CGPointMake(1, 1)
return gradient
}

let circle = setupCircle()
let gradient = setupGradient()

gradient.frame = circle.bounds
gradient.mask = circle

progressView.layer.addSublayer(circle)
progressView.layer.insertSublayer(gradient, atIndex: 0)


EDIT:
I've changed setupCircle method to:

private func setupCircle() -> CAShapeLayer {
let circleLayer = CAShapeLayer()
circleLayer.frame = progressView.bounds.insetBy(dx: 5, dy: 5)
circleLayer.path = UIBezierPath(ovalInRect: CGRectMake(progressView.center.x, progressView.center.y, progressView.bounds.size.width, progressView.bounds.size.height)).CGPath
circleLayer.fillColor = nil
circleLayer.strokeColor = UIColor.yellowColor().CGColor
circleLayer.backgroundColor = UIColor.yellowColor().CGColor
circleLayer.lineWidth = configurator.lineWidth
circleLayer.strokeEnd = 1.0

return circleLayer
}


And here the effect, did I do it properly?:



EDIT 2:
I've changed
circleLayer.path = UIBezierPath(ovalInRect: CGRectMake(progressView.center.x, progressView.center.y, progressView.bounds.size.width, progressView.bounds.size.height)).CGPath
to
circleLayer.path = UIBezierPath(ovalInRect: circleLayer.bounds).CGPath
and it's better but still not what I want:

enter image description here

Answer

It's ok that you create layers and add them to layer hierarchy in viewDidLoad, but you shouldn't do any frame logic there since layout is not updated at that moment. Try to move all the code which is doing any frame calculations to viewDidLayoutSubviews.

This should also be done every time layout is updated, so move it from setupCircle to viewDidLayoutSubviews:

let circlePath = UIBezierPath(...)
circleLayer.path = circlePath.cgPath

Edit:

Also I can't see where do you set circleLayer's frame? It's not enough to set it's path. If you have CGRect.zero rect for a mask, it will completely hide it's parent layer.

I would recommend to replace this:

let circlePath = UIBezierPath(arcCenter: CGPoint(x: progressView.frame.size.width / 2.0, y: progressView.frame.size.height / 2.0), radius: (progressView.frame.size.width - 10)/2, startAngle: 0.0, endAngle: CGFloat(M_PI * 2.0), clockwise: true)

with this:

circleLayer.bounds = progressView.frame.insetBy(dx: 5, dy: 5)
circleLayer.path = UIBezierPath(ovalIn: circleLayer.bounds).cgPath

This way you'll have both frame and path set. Also be careful about coordinate system. I'm not sure about where is progressView in your view hierarchy, perhaps you'll need it's bounds instead.

Edit 2: Also you should delete a few lines of code there.

  1. This makes your circle layer completely opaque, it makes the shape itself unvisible.

circleLayer.backgroundColor = UIColor.yellowColor().CGColor 
  1. You shouldn't add circle as a sublayer since you're going to use it as a mask:

progressView.layer.addSublayer(circle)