Search code examples
swiftxcodeuiviewcalayercgpath

How to center the CAShapeLayer path in the shape layer


I created a DrawView where it's possible to draw. I created an instance of UIBezierPath for drawing.

// This function is called when the user has finished drawing.
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
    let shapeLayer = CAShapeLayer()

        //The CAShapeLayer has the same path of the drawing (currentPath is the instance of UIBezierPath).
        shapeLayer.path = currentPath?.cgPath
        shapeLayer.strokeColor = UIColor.black.cgColor
        shapeLayer.fillColor = UIColor.clear.cgColor
        shapeLayer.lineWidth = 10.0

        //Here I set the shapeLayer on the drawingView (the blue one that you can see in the image)
        drawingView.layer.addSublayer(shapeLayer)
        shapeLayer.position = CGPoint(x: drawingView.layer.bounds.midX, y: drawingView.layer.bounds.midY)
        shapeLayer.backgroundColor = UIColor.blue.cgColor
        shapeLayer.frame = drawingView.layer.bounds
}

The problem is that the path (e.g number 3 in the image) is not centered on its shapeLayer.

shapeLayer.path?.boundingBoxOfPath = (289.5, 349.5, 525.0, 129.0)

shapeLayer.frame = (0.0, 0.0, 200.0, 200.0)

shapeLayer.position = (100.0, 100.0)

drawingView.frame = (0.0, 912.0, 200.0, 200.0)

enter image description here

Any hint? thank you


Solution

  • Forget the drawing view and just think about how to center a path in a shape layer of known size. Here's a deliberate failure:

        let path = UIBezierPath(ovalIn: CGRect(x: 50, y: 50, width: 50, height: 50))
        let lay = CAShapeLayer()
        lay.frame = CGRect(x: 40, y: 40, width: 200, height: 200)
        lay.backgroundColor = UIColor.red.cgColor
        lay.path = path.cgPath
        self.view.layer.addSublayer(lay)
    

    We get this:

    enter image description here

    The filled circle is not centered in the red shape layer. Okay, we know why, because we know the bezier path that created the filled circle. But let's say we don't know that. We can still center the shape in the shape layer, like this:

        let path = UIBezierPath(ovalIn: CGRect(x: 50, y: 50, width: 50, height: 50))
        let lay = CAShapeLayer()
        lay.frame = CGRect(x: 40, y: 40, width: 200, height: 200)
        lay.backgroundColor = UIColor.red.cgColor
    
        let cgpath = path.cgPath
        let box = cgpath.boundingBoxOfPath
        let xtarget = (lay.bounds.width - box.width)/2
        let ytarget = (lay.bounds.height - box.height)/2
        let xoffset = xtarget - box.minX
        let yoffset = ytarget - box.minY
        var transform = CGAffineTransform(translationX: xoffset, y: yoffset)
        let cgpath2 = cgpath.copy(using: &transform)
        lay.path = cgpath2
    
        self.view.layer.addSublayer(lay)
    

    Result:

    enter image description here

    So, given a CGPath, and given a shape layer whose final bounds are known, you can use that technique to center the path in the shape layer. And those are circumstances that apply perfectly to your use case.