如何在 CAShapeLayer 上只填充形状的一部分

How to fill only a part of the shape on CAShapeLayer

我正在使用 UIBezierPath 创建以下形状,然后使用 CAShapeLayer 绘制它,如下所示。

然后我可以将 CAShapeLayer fillColor 属性 从 shapeLayer.fillColor = UIColor.clear.cgColor 更改为 shapeLayer.fillColor = UIColor.blue.cgColor 并获得以下形状。

代码如下:

import UIKit

class ViewController: UIViewController {

    var line = [CGPoint]()
    let bezierPath: UIBezierPath = UIBezierPath()

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .white
        let point1 = CGPoint(x: 100, y: 100)
        let point2 = CGPoint(x: 400, y: 100)

        line.append(point1)
        line.append(point2)

        addCircle(toRight: false)
        addLine()
        addCircle(toRight: true)

        let shapeLayer = CAShapeLayer()
        shapeLayer.path = bezierPath.cgPath
        shapeLayer.strokeColor = UIColor.blue.cgColor
        shapeLayer.fillColor = UIColor.clear.cgColor

         self.view.layer.addSublayer(shapeLayer)
    }

    func addLine() -> Void {
        bezierPath.move(to: line.first!)
        bezierPath.addLine(to: line.last!)
    }

    func addCircle(toRight: Bool) -> Void {
        let angle = CGFloat( Double.pi / 180 )
        let r = CGFloat(20.0)
        let x0 = toRight ? line.first!.x : line.last!.x
        let y0 = toRight ? line.first!.y : line.last!.y
        let x1 =  toRight ? line.last!.x : line.first!.x
        let y1 = toRight ? line.last!.y : line.first!.y
        let x = x1 - x0
        let y = y1 - y0
        let h = (x*x + y*y).squareRoot()
        let x2 = x0 + (x * (h + r) / h)
        let y2 = y0 + (y * (h + r) / h)

        // Add the arc, starting at that same point
        let point2 = CGPoint(x: x2, y: y2)
        let pointZeroDeg = CGPoint(x: x2 + r, y: y2)
        self.bezierPath.move(to: pointZeroDeg)
        self.bezierPath.addArc(withCenter: point2, radius: r,
                          startAngle: 0*angle, endAngle: 360*angle,
                          clockwise: true)
        self.bezierPath.close()
    }
}

但我其实想要的是下面的形状(左边的圆是填充的,右边的圆是空的)。

所以我的问题是,如何在 CAShapeLayer 上只填充形状的一部分? 可能吗?有什么技巧可以实现吗?

PS: 我可以通过创建 3 个不同的 UIBezierPaths(用于 leftCircle、line 和 rightCircle)并使用 3 个不同的 CAShapeLayers 绘制它们来实现这一点,如下所示。

// left Circle
shapeLayer1.path = bezierPath1.cgPath
shapeLayer1.fillColor = UIColor.blue.cgColor

// line
shapeLayer2.path = bezierPath2.cgPath

// right Circle
shapeLayer3.path = bezierPath3.cgPath
shapeLayer3.fillColor = UIColor.clear.cgColor

self.view.layer.addSublayer(shapeLayer1)
self.view.layer.addSublayer(shapeLayer2)
self.view.layer.addSublayer(shapeLayer3)

但我更喜欢使用单个 CAShapeLayer 来实现这一点。

我认为更好的方法是使用多种形状。

然而,您可以使用 evenOdd 形状填充规则来实现单一形状。

在方法addCircle:

右边的圆上多加​​一个圆就可以了
if toRight {
    self.bezierPath.addArc(withCenter: point2, radius: r,
                      startAngle: 0*angle, endAngle: 2*360*angle,
                      clockwise: true)
} else {
    self.bezierPath.addArc(withCenter: point2, radius: r,
                      startAngle: 0*angle, endAngle: 360*angle,
                      clockwise: true)
}

并将fillRule设置为evenOdd:

shapeLayer.fillColor = UIColor.blue.cgColor        
shapeLayer.fillRule = .evenOdd

所以你会得到左边的圆圈将有一个绘图圆圈,并将由 evenOdd 规则填充。但是右边的圆圈会有两个画圈,会空出来。

evenOdd规则(https://developer.apple.com/documentation/quartzcore/cashapelayerfillrule/1521843-evenodd):

Specifies the even-odd winding rule. Count the total number of path crossings. If the number of crossings is even, the point is outside the path. If the number of crossings is odd, the point is inside the path and the region containing it should be filled.