1

I have come across a problem where I try to set a simple path on a CAShapeLayer which in turn resides in a 3D transformed parent view/layer. For some of the transformations that I want to use the layer will stroke a very different path than what is filled with the fill color.

I have created a sample that will show this effect. The artefacts can be seen with different kind of shapes and transformations, at least if the shape include Bezier curves and the transformation has perspective and rotation.

Is there a solution to this? I am fine with the filled region being somewhat distorted as long as the stroke follows the filled path.

// Create a single view app and paste this as the main view controller to test
class ViewController: UIViewController {

    var shapeView: UIView!

    override func viewDidLoad() {
        super.viewDidLoad()

        shapeView = UIView(frame: CGRect(x: 100, y: 100, width: 100, height: 100))
        let layer = createShapeLayer()
        layer.frame = shapeView.bounds
        layer.path = createPath()
        shapeView.layer.addSublayer(layer)
        shapeView.layer.transform = getTransform()
        view.addSubview(shapeView)
    }

    func createShapeLayer() -> CAShapeLayer {
        let layer = CAShapeLayer()
        layer.fillColor = UIColor.red.cgColor
        layer.strokeColor = UIColor.black.cgColor
        layer.lineWidth = 5

        return layer
    }

    func getTransform() -> CATransform3D {
        var transform = CATransform3DIdentity
        transform.m34 = 1.0 / -76.28
        transform = CATransform3DTranslate(transform, 0.0, 59.59, 50.45)
        transform = CATransform3DRotate(transform, 147.43 * .pi / 180.0, 1.0, 0.0, 0.0)

        return transform
    }

    // Simple banana shape
    func createPath() -> CGPath {
        let path = CGMutablePath()
        path.move(to: CGPoint(x: 45, y: 0))
        let cp1 = CGPoint(x: 50, y: -25)
        path.addCurve(to: CGPoint(x: 55, y: 0), control1: cp1, control2: cp1)
        let cp2 = CGPoint(x: 80, y: 50)
        path.addCurve(to: CGPoint(x: 55, y: 100), control1: cp2, control2: cp2)
        let cp3 = CGPoint(x: 50, y: 125)
        path.addCurve(to: CGPoint(x: 45, y: 100), control1: cp3, control2: cp3)
        let cp4 = CGPoint(x: 70, y: 50)
        path.addCurve(to: CGPoint(x: 45, y: 0), control1: cp4, control2: cp4)

        return path
    }
}

This is what the output looks like. A single shape with red fill color and black stroke color.

I suspect that the problem is that the filling algorithm is breaking down in some circumstances while the stroking algorithm does not. The region that has to be filled becomes very large in the Z direction, although small on the screen. However, ideally I think the stroke should always fall on the edge of the filled region, even if it becomes distorted.

Result

LGP
  • 4,135
  • 1
  • 22
  • 34

0 Answers0