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
以保持您的路径。随着用户的手势,应用变换;然后在手势完成时重新创建路径。在更高级的用法中,您可能会更频繁地重新计算实际路径,而不仅仅是在用户停止时(例如,如果他们暂停而不放手),但这些都是改进。
几个想法:
为每个像素重建路径可能是一个昂贵的过程。降低此成本的一种方法是减少渲染的数据点数量。例如,如果您正在调整 scale
并重建整个路径,您可能希望在更新路径中间手势时使用一些 step
因素,并将 for
循环替换为:
let step = 4
for i in stride(from: 0, through: Int(bounds.size.width * contentScaleFactor), by: step) {
...
}
然后,当手势结束时,您可能会再次更新路径,step
为 1
。
另一种方法是不更新scale
并每次都调用setNeedsDisplay
,而只是更新视图的 transform
。 (这在放大时会导致超快的行为,但在缩小时显然会出现问题,因为不会渲染从路径中排除的部分。)
如果您正在对此进行测试,请确保测试在物理设备上构建的版本(即优化版)。调试构建及其所有安全检查确实会减慢该过程,这将在像这样的计算密集型项目中脱颖而出。
对了,小路的楼貌似被埋在了draw(rect:)
。我会将 UIBezierPath
的构建与路径的描边分离,因为虽然,是的,每次更新函数时,您可能想要更新并绘制路径,但您绝对不想重新构建每次调用 draw(rect:)
时的路径。
一旦你这样做了,draw(rect:)
甚至可能都不需要了。您可能只添加一个 CAShapeLayer
作为视图图层的子图层,设置该形状图层的 strokeColor
和 strokeWidth
,然后更新其 path
.
如果您能够将平移(平移)和缩放描述为 CGAffineTransform
,那么您可以使用 apply(_:)
一次性将其应用到您的 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
以保持您的路径。随着用户的手势,应用变换;然后在手势完成时重新创建路径。在更高级的用法中,您可能会更频繁地重新计算实际路径,而不仅仅是在用户停止时(例如,如果他们暂停而不放手),但这些都是改进。
几个想法:
为每个像素重建路径可能是一个昂贵的过程。降低此成本的一种方法是减少渲染的数据点数量。例如,如果您正在调整
scale
并重建整个路径,您可能希望在更新路径中间手势时使用一些step
因素,并将for
循环替换为:let step = 4 for i in stride(from: 0, through: Int(bounds.size.width * contentScaleFactor), by: step) { ... }
然后,当手势结束时,您可能会再次更新路径,
step
为1
。另一种方法是不更新
scale
并每次都调用setNeedsDisplay
,而只是更新视图的transform
。 (这在放大时会导致超快的行为,但在缩小时显然会出现问题,因为不会渲染从路径中排除的部分。)如果您正在对此进行测试,请确保测试在物理设备上构建的版本(即优化版)。调试构建及其所有安全检查确实会减慢该过程,这将在像这样的计算密集型项目中脱颖而出。
对了,小路的楼貌似被埋在了
draw(rect:)
。我会将UIBezierPath
的构建与路径的描边分离,因为虽然,是的,每次更新函数时,您可能想要更新并绘制路径,但您绝对不想重新构建每次调用draw(rect:)
时的路径。一旦你这样做了,
draw(rect:)
甚至可能都不需要了。您可能只添加一个CAShapeLayer
作为视图图层的子图层,设置该形状图层的strokeColor
和strokeWidth
,然后更新其path
.
如果您能够将平移(平移)和缩放描述为 CGAffineTransform
,那么您可以使用 apply(_:)
一次性将其应用到您的 UIBezierPath
。