Removing lagging latency during continuous period of drawing UIBezierPath in Swift

独自空忆成欢 提交于 2019-12-23 19:35:10

问题


The code below draws lines by overriding touches, however lagging begins to occur over a period of continuous non stop drawing. This lagging accumulates and gets worse the longer the finger remains moving across the screen. The result is the CPU nearly maxes out on an actual device (CPU 98%+) and the resulting image looks choppy the longer the drawing continues.

Also, when drawing extra fast especially in circles, there is a discrepancy in the paths drawn between the path and temporaryPath (or localPath). Although they are being drawn at different times, they seem to appear on screen simultaneously which is visually distracting seeing two paths drawing fast. The inner path (path) appears to be a distance away from the outer path (temporaryPath) highlighted in red in one of the below images.

1 - How can the lagging latency over a period of continuous drawing be eliminated?

2 - How can the discrepancy in the paths drawn be eliminated?

3 - How can the alpha/opacity of the path and temporaryPath be changed?

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:


You asked:

  1. How can the lagging latency over a period of continuous drawing be eliminated?

As you correctly surmised, yes, doing a snapshot and resetting the path can fix this by limiting how long the path will be.

I know you're aware of this, but for the benefit of other readers, in iOS 9 you can use predictive touches, too. In this particular algorithm (where (a) you're simply adding to a path, but (b) every fourth point is adjusted on the basis of the next point to ensure that there are no discontinuities where the two cubic bezier curves join) that's a little tricky, but could be done.

  1. How can the discrepancy in the paths drawn be eliminated?

This is caused because the snapshot is including the temporary path. But the whole purpose of that temporary path is that it will be discarded as more points come in. So you shouldn't include it in the snapshot you create mid-gesture.

So, I'd suggest adding a parameter to the snapshot function which indicates whether the temporaryPath should be included or not. When calling it mid-gesture, you'd specify includeTemporaryPath as false, but when calling it at the end of the gesture, includeTemporaryPath would be true.

For example:

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 that path generation code, I realized it could be streamlined a bit. I also fixed a bug, too. See above code.

You then asked:

  1. How can the alpha/opacity of the path and temporaryPath be changed?

You can just adjust the color with which you call setStroke with the appropriate alpha. For example, if you wanted the temporary path to be at half of the alpha of the 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()
}


来源:https://stackoverflow.com/questions/35067811/removing-lagging-latency-during-continuous-period-of-drawing-uibezierpath-in-swi

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!