UIBezierPath 的快速替代品

Fast alternatives for UIBezierPath

我正在制作可以构建图表的应用程序。 我正在使用 UIBezierPath,一切都很好,直到我决定使用手势移动和缩放图形成为可能。

这是我的画法:

var isFirstPoint = true
        color.set()
        funcPath.lineWidth = width

        for i in 0...Int(bounds.size.width * contentScaleFactor) {
            let cgX = CGFloat(i) / contentScaleFactor
            let funcX = Double ((cgX - bounds.width / 2) / scale)

            let funcY = function(funcX) * Double(scale)

            guard !funcY.isNaN else { continue }
            let cgY = -( CGFloat(funcY) - bounds.height / 2)

            if isFirstPoint {
                funcPath.move(to: CGPoint(x: cgX, y: cgY))
                isFirstPoint = false
            } else {
                if cgY > max(bounds.size.width, bounds.size.height) * 2 {
                    isFirstPoint = true
                } else {
                    funcPath.addLine(to: CGPoint(x: cgX, y: cgY) )
                }
            }
        }
funcPath.stroke()

有没有更快的方法?

通常移动和缩放是通过对图层应用变换来完成的。查看 CAShapeLayer 以保持您的路径。随着用户的手势,应用变换;然后在手势完成时重新创建路径。在更高级的用法中,您可能会更频繁地重新计算实际路径,而不仅仅是在用户停止时(例如,如果他们暂停而不放手),但这些都是改进。

几个想法:

  1. 为每个像素重建路径可能是一个昂贵的过程。降低此成本的一种方法是减少渲染的数据点数量。例如,如果您正在调整 scale 并重建整个路径,您可能希望在更新路径中间手势时使用一些 step 因素,并将 for 循环替换为:

    let step = 4
    for i in stride(from: 0, through: Int(bounds.size.width * contentScaleFactor), by: step) {
        ...
    }
    

    然后,当手势结束时,您可能会再次更新路径,step1

  2. 另一种方法是更新scale并每次都调用setNeedsDisplay,而只是更新视图的 transform。 (这在放大时会导致超快的行为,但在缩小时显然会出现问题,因为不会渲染从路径中排除的部分。)

  3. 如果您正在对此进行测试,请确保测试在物理设备上构建的版本(即优化版)。调试构建及其所有安全检查确实会减慢该过程,这将在像这样的计算密集型项目中脱颖而出。

  4. 对了,小路的楼貌似被埋在了draw(rect:)。我会将 UIBezierPath 的构建与路径的描边分离,因为虽然,是的,每次更新函数时,您可能想要更新并绘制路径,但您绝对不想重新构建每次调用 draw(rect:) 时的路径。

  5. 一旦你这样做了,draw(rect:) 甚至可能都不需要了。您可能只添加一个 CAShapeLayer 作为视图图层的子图层,设置该形状图层的 strokeColorstrokeWidth,然后更新其 path.

如果您能够将平移(平移)和缩放描述为 CGAffineTransform,那么您可以使用 apply(_:) 一次性将其应用到您的 UIBezierPath