在Swift中绘制UIBezierPath的连续期间,消除滞后延迟

下面的代码通过覆盖触摸来绘制线条,但是在连续的不停止绘图的时间段内滞后开始发生。 手指在屏幕上移动的时间越长,这种滞后越积累越差。 结果是CPU在实际设备(CPU 98%+)上几乎达到最大值,并且随着绘图的持续时间越长,所得到的图像看起来不稳定。

而且,特别是在绘制特别快的圆圈时, pathtemporaryPath (或localPath )之间绘制的path存在差异。 虽然他们在不同的时间被画出来,但他们似乎同时出现在屏幕上,这看起来似乎分散注意力,看到两条path快速拉近。 内部path( path )显示为与下面的一个图像中以红色突出显示的外部path( temporaryPath )的距离。

1 – 如何消除一段连续绘图的滞后延迟?

2 – 如何消除path上的差异?

3 – 如何改变pathtemporaryPath path的alpha / opacity?

在这里输入图像说明

 class swiftView: UIView { var strokeColor = UIColor.blueColor() var lineWidth: CGFloat = 5 var snapshotImage: UIImage? private var path: UIBezierPath? private var temporaryPath: UIBezierPath? private var points = [CGPoint]() var counterPoints:Int? required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) } override func drawRect(rect: CGRect) { autoreleasepool { snapshotImage?.drawInRect(rect) strokeColor.setStroke() path?.stroke() temporaryPath?.stroke() } } override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) { let touch: AnyObject? = touches.first points = [touch!.locationInView(self)] counterPoints = 0 } override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) { let touch: AnyObject? = touches.first let point = touch!.locationInView(self) points.append(point) let pointCount = points.count counterPoints = counterPoints! + 1 if pointCount == 2 { temporaryPath = createPathStartingAtPoint(points[0]) temporaryPath?.addLineToPoint(points[1]) setNeedsDisplay() } else if pointCount == 3 { temporaryPath = createPathStartingAtPoint(points[0]) temporaryPath?.addQuadCurveToPoint(points[2], controlPoint: points[1]) setNeedsDisplay() } else if pointCount == 4 { temporaryPath = createPathStartingAtPoint(points[0]) temporaryPath?.addCurveToPoint(points[3], controlPoint1: points[1], controlPoint2: points[2]) // setNeedsDisplay() if counterPoints! < 50 { self.setNeedsDisplay() } else { temporaryPath = nil self.constructIncrementalImage() path = nil self.setNeedsDisplay() counterPoints = 0 } } else if pointCount == 5 { points[3] = CGPointMake((points[2].x + points[4].x)/2.0, (points[2].y + points[4].y)/2.0) // create a quad bezier up to point 4, too if points[4] != points[3] { let length = hypot(points[4].x - points[3].x, points[4].y - points[3].y) / 2.0 let angle = atan2(points[3].y - points[2].y, points[4].x - points[3].x) let controlPoint = CGPoint(x: points[3].x + cos(angle) * length, y: points[3].y + sin(angle) * length) temporaryPath = createPathStartingAtPoint(points[3]) temporaryPath?.addQuadCurveToPoint(points[4], controlPoint: controlPoint) } else { temporaryPath = nil } if path == nil { path = createPathStartingAtPoint(points[0]) } path?.addCurveToPoint(points[3], controlPoint1: points[1], controlPoint2: points[2]) self.setNeedsDisplay() points = [points[3], points[4]] } } override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) { self.constructIncrementalImage() path = nil self.setNeedsDisplay() counterPoints = 0 } override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) { self.touchesEnded(touches!, withEvent: event) } private func createPathStartingAtPoint(point: CGPoint) -> UIBezierPath { let localPath = UIBezierPath() localPath.moveToPoint(point) localPath.lineWidth = lineWidth localPath.lineCapStyle = .Round localPath.lineJoinStyle = .Round return localPath } private func constructIncrementalImage() { UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, 0.0) strokeColor.setStroke() snapshotImage?.drawAtPoint(CGPointZero) path?.stroke() temporaryPath?.stroke() snapshotImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() } } 

你问:

  1. 如何消除一段连续绘图的滞后延迟?

正如你正确地猜测,是的,做一个快照和重置path可以通过限制path的时间来解决这个问题。

我知道你知道这一点,但为了其他读者的利益,在iOS 9中,你也可以使用预测性触摸。 在这个特定的algorithm中(其中(a)只是简单地join到一条path中,但是(b)每一个第四点在下一个点的基础上进行调整以确保没有两个三次贝塞尔曲线连接的不连续性)有点棘手,但可以做到。

  1. 如何消除path上的差异呢?

这是因为快照包含临时path。 但是这个临时path的全部目的是随着更多点的进入而被丢弃。所以你不应该把它包含在你创build的快照中。

所以,我build议在快照函数中添加一个参数来指示temporaryPathpath是否应该被包含。 当调用中间手势时,您将指定includeTemporaryPathfalse ,但在手势结束时调用它时, includeTemporaryPath将为true

例如:

 class SmoothCurvedLinesView: UIView { var strokeColor = UIColor.blueColor() var lineWidth: CGFloat = 20 var snapshotImage: UIImage? private var path: UIBezierPath? private var temporaryPath: UIBezierPath? private var points = [CGPoint]() private var totalPointCount = 0 override func drawRect(rect: CGRect) { snapshotImage?.drawInRect(rect) strokeColor.setStroke() path?.stroke() temporaryPath?.stroke() } override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) { let touch: AnyObject? = touches.first points = [touch!.locationInView(self)] totalPointCount = totalPointCount + 1 } override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) { let touch: AnyObject? = touches.first let point = touch!.locationInView(self) points.append(point) totalPointCount = totalPointCount + 1 updatePaths() if totalPointCount > 50 { constructIncrementalImage(includeTemporaryPath: false) path = nil totalPointCount = 0 } setNeedsDisplay() } private func updatePaths() { // update main path while points.count > 4 { points[3] = CGPointMake((points[2].x + points[4].x)/2.0, (points[2].y + points[4].y)/2.0) if path == nil { path = createPathStartingAtPoint(points[0]) } path?.addCurveToPoint(points[3], controlPoint1: points[1], controlPoint2: points[2]) points.removeFirst(3) } // build temporary path up to last touch point let pointCount = points.count if pointCount == 2 { temporaryPath = createPathStartingAtPoint(points[0]) temporaryPath?.addLineToPoint(points[1]) } else if pointCount == 3 { temporaryPath = createPathStartingAtPoint(points[0]) temporaryPath?.addQuadCurveToPoint(points[2], controlPoint: points[1]) } else if pointCount == 4 { temporaryPath = createPathStartingAtPoint(points[0]) temporaryPath?.addCurveToPoint(points[3], controlPoint1: points[1], controlPoint2: points[2]) } } override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) { constructIncrementalImage() path = nil temporaryPath = nil setNeedsDisplay() } override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) { touchesEnded(touches!, withEvent: event) } private func createPathStartingAtPoint(point: CGPoint) -> UIBezierPath { let localPath = UIBezierPath() localPath.moveToPoint(point) localPath.lineWidth = lineWidth localPath.lineCapStyle = .Round localPath.lineJoinStyle = .Round return localPath } private func constructIncrementalImage(includeTemporaryPath includeTemporaryPath: Bool = true) { UIGraphicsBeginImageContextWithOptions(bounds.size, false, 0.0) strokeColor.setStroke() snapshotImage?.drawAtPoint(CGPointZero) path?.stroke() if (includeTemporaryPath) { temporaryPath?.stroke() } snapshotImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() } } 

顺便说一下,虽然我是提供path生成代码的人,但我意识到可以简化一下。 我也修正了一个错误。 见上面的代码。

然后你问:

  1. 如何改变path和临时path的alpha /不透明度?

您可以调整用相应的alpha调用setStroke的颜色。 例如,如果您希望临时path位于主path的一半,则可以执行如下操作:

 override func drawRect(rect: CGRect) { snapshotImage?.drawInRect(rect) strokeColor.setStroke() path?.stroke() strokeColor.colorWithAlphaComponent(0.5).setStroke() temporaryPath?.stroke() }