Remove delay delays during a continuous UIBezierPath drawing period in Swift

The code below draws lines by redefining strokes, however, lag begins to occur during the period of continuous continuous drawing. This lag accumulates and worsens the longer the finger remains moving across the screen. As a result, the CPU is almost maximized on the actual device (CPU 98% +), and the resulting image looks intermittent, the longer the drawing continues.

In addition, when drawing ultrafast, especially in circles, there is a mismatch in the tracks drawn between path and temporaryPath (or localPath ). Although they are drawn at different times, they seem to appear on the screen at the same time, which is visually distracting, seeing two ways of drawing quickly. The inner path ( path ) appears to be located at a distance from the outer path ( temporaryPath ) highlighted in red in one of the following images.

1 - How can I fix latency latency during the continuous drawing period?

2 - How can you eliminate the inconsistency in the drawn paths?

3 - How can I change the alpha / opacity of path and temporaryPath ?

enter image description here

 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() } } 
+3
source share
1 answer

You asked:

  • How can latency latency during the continuous painting period be eliminated?

As you correctly understood, yes, by taking a picture and dropping the path, you can fix this by limiting how long the path will be.

I know that you know about it, but for other readers, in iOS 9, you can also use predictive touches. In this particular algorithm (where (a) you just add a path, but (b) every fourth point is adjusted based on the next point to make sure there are no gaps where the two cubic bezel-less curves connect), which is a bit more complicated, but can be done .

  1. How can you eliminate the mismatch in the drawn paths?

This is because the snapshot includes a time path. But the goal of this time path is that it will be discarded as more points arrive. Therefore, you should not include it in the snapshot that you create in the middle of gestures.

So, I would suggest adding a parameter to the snapshot function, which indicates whether to include temporaryPath or not. When you call it in the middle of gestures, you must specify includeTemporaryPath as false , but when called at the end of the gesture, includeTemporaryPath will be true .

For instance:

 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() } } 

By the way, while I was the one who provided the code for generating the code, I realized that it could be simplified. I also fixed the error. See code above.

Then you asked:

  1. How to change alpha / opacity of a path and temporary table?

You can simply adjust the color with which you call setStroke with the corresponding alpha. For example, if you want the temporary path to be in half alpha main path, you could do something like:

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

All Articles