iosswiftuibezierpathdrawrectcashapelayer

Maintaining UIBezierPath above CAShapeLayer


I have a circle added to CAShapeLayer and a line with UIBeizerPath. I want to maintain the position of line to be above circle. Basically in drawRect, position of the items will be maintained one after the other. So, I call drawCircle() first and invoke drawLine() expecting that line has to be on top of the circle. Here is my code.

class DrawView: UIView {
    override func draw(_ rect: CGRect) {
        drawCircle()
        drawLine()
    }
    
    private func drawCircle() {
        if let sublayers = layer.sublayers {
            for layer in sublayers where layer.name == "Circle" {
                layer.removeFromSuperlayer()
            }
        }
        
        let circlePath = UIBezierPath(arcCenter: CGPoint(x: bounds.midX, y: 400),
                                      radius: 20.0,
                                      startAngle: 0,
                                      endAngle: CGFloat(Double.pi * 2),
                                      clockwise: true)
        let shapeLayer = CAShapeLayer()
        shapeLayer.name = "Circle"
        shapeLayer.path = circlePath.cgPath
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.strokeColor = UIColor.red.cgColor
        shapeLayer.lineWidth = 3.0
            
        layer.addSublayer(shapeLayer)
    }
    
    private func drawLine() {
        let path = UIBezierPath()
        
        path.move(to: CGPoint(x:bounds.midX, y:0))
        path.addLine(to: CGPoint(x: bounds.midX, y: 400))
        path.close()
        
        UIColor.blue.set()
        path.lineWidth = 20.0
        path.stroke()
    }
}

and below is the result

enter image description here

But, adding the line to another CAShapeLayer gives the expected result, adds the line on top of circle. How can I bring the line on top of circle without adding the line to a CAShapeLayer.


Solution

  • You can render the circle layer in the current graphics context:

    shapeLayer.render(in: UIGraphicsGetCurrentContext()!)
    // instead of
    // layer.addSublayer(shapeLayer)
    

    This essentially "draws" the layer in the same way that UIBezierPath.stroke draws things. So the things that are drawn first appear "below" the things that are drawn later.

    UIGraphicsGetCurrentContext will not return nil here because you are calling this from draw.

    That said, I would recommend that you either do everything with layers (and update the layers in layoutSubviews) or only draw things in draw. Stick to one of them. Mixing the two makes the code messier, in my opinion.