Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
378 views
in Technique[技术] by (71.8m points)

ios - Removing lagging latency in drawing UIBezierPath smooth lines in Swift

The code below draws smooth curved lines by overriding touches, but there is noticeable lagging or latency. The code uses addCurveToPoint and calls setNeedsDisplay after every 4 touch points which causes a jumpy appearance as the drawing doesn't keep up with finger movements. To remove the lagging or perceived latency, touch points 1, 2, 3 (leading up to touch point 4) could be temporarily filled with addQuadCurveToPoint and addLineToPoint.

  1. How can this actually be achieved in code to remove perceived lagging by using a temporary Line and QuadCurved line before displaying a final Curved line?

  2. If the below class is attached to one UIView (e.g. viewOne or self), how do I make a copy of the drawing to another UIView outside the class (e.g. viewTwo) after touchesEnded?

     //  ViewController.swift
    
    import UIKit
    
    class drawSmoothCurvedLinesWithLagging: UIView {
    
        let path=UIBezierPath()
        var incrementalImage:UIImage?
    
        var points = [CGPoint?](count: 5, repeatedValue: nil)
    
        var counter:Int?
    
        var strokeColor:UIColor?
    
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
        }
    
        override func drawRect(rect: CGRect) {
            autoreleasepool {
                incrementalImage?.drawInRect(rect)
                strokeColor = UIColor.blueColor()
                strokeColor?.setStroke()
                path.lineWidth = 20
                path.lineCapStyle = CGLineCap.Round
                path.stroke()
            }
        }
    
        override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
            counter = 0
    
            let touch: AnyObject? = touches.first
            points[0] = touch!.locationInView(self)
        }
    
        override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
            let touch: AnyObject? = touches.first
            let point = touch!.locationInView(self)
    
            counter = counter! + 1
            points[counter!] = point
    
    
            if counter == 2{
                //use path.addLineToPoint ?
                //use self.setNeedsDisplay() ?
            }
    
            if counter == 3{
                //use path.addQuadCurveToPoint ?
                //use self.setNeedsDisplay() ?
            }
    
            if counter == 4{
                points[3]! = CGPointMake((points[2]!.x + points[4]!.x)/2.0, (points[2]!.y + points[4]!.y)/2.0)
                path.moveToPoint(points[0]!)
                path.addCurveToPoint(points[3]!, controlPoint1: points[1]!, controlPoint2: points[2]!)
    
                self.setNeedsDisplay()
    
                points[0]! = points[3]!
                points[1]! = points[4]!
                counter = 1
            }
        }
    
        override func touchesEnded(touches: Set<UITouch>, withEvent event: UIEvent?) {
            self.drawBitmap()
            self.setNeedsDisplay()
            path.removeAllPoints()
            counter = 0
        }
    
        override func touchesCancelled(touches: Set<UITouch>?, withEvent event: UIEvent?) {
            self.touchesEnded(touches!, withEvent: event)
        }
    
        func drawBitmap(){
            UIGraphicsBeginImageContextWithOptions(self.bounds.size, true, 0.0)
            strokeColor?.setStroke()
            if((incrementalImage) == nil){
                let rectPath:UIBezierPath = UIBezierPath(rect: self.bounds)
                UIColor.whiteColor().setFill()
                rectPath.fill()
            }
    
            incrementalImage?.drawAtPoint(CGPointZero)
            path.stroke()
            incrementalImage = UIGraphicsGetImageFromCurrentImageContext()
            UIGraphicsEndImageContext()
        }
    
    }
    
    class ViewController: UIViewController {
    
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view, typically from a nib.
        }
    
        override func didReceiveMemoryWarning() {
            super.didReceiveMemoryWarning()
            // Dispose of any resources that can be recreated.
        }
    
    
    }
    
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)
  1. Yes, adding a curve every few points will give it a stuttering lag. So, yes, you can reduce this affect by adding a line to points[1], adding a quad curve to points[2] and adding a cubic curve to points[3].

    As you said, make sure to add this to a separate path, though. So, in Swift 3/4:

    class SmoothCurvedLinesView: UIView {
        var strokeColor = UIColor.blue
        var lineWidth: CGFloat = 20
        var snapshotImage: UIImage?
    
        private var path: UIBezierPath?
        private var temporaryPath: UIBezierPath?
        private var points = [CGPoint]()
    
        override func draw(_ rect: CGRect) {
            snapshotImage?.draw(in: rect)
    
            strokeColor.setStroke()
    
            path?.stroke()
            temporaryPath?.stroke()
        }
    
        override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
            if let touch = touches.first {
                points = [touch.location(in: self)]
            }
        }
    
        override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
            guard let touch = touches.first else { return }
            let point = touch.location(in: self)
    
            points.append(point)
    
            updatePaths()
    
            setNeedsDisplay()
        }
    
        private func updatePaths() {
            // update main path
    
            while points.count > 4 {
                points[3] = CGPoint(x: (points[2].x + points[4].x)/2.0, y: (points[2].y + points[4].y)/2.0)
    
                if path == nil {
                    path = createPathStarting(at: points[0])
                }
    
                path?.addCurve(to: points[3], controlPoint1: points[1], controlPoint2: points[2])
    
                points.removeFirst(3)
    
                temporaryPath = nil
            }
    
            // build temporary path up to last touch point
    
            if points.count == 2 {
                temporaryPath = createPathStarting(at: points[0])
                temporaryPath?.addLine(to: points[1])
            } else if points.count == 3 {
                temporaryPath = createPathStarting(at: points[0])
                temporaryPath?.addQuadCurve(to: points[2], controlPoint: points[1])
            } else if points.count == 4 {
                temporaryPath = createPathStarting(at: points[0])
                temporaryPath?.addCurve(to: points[3], controlPoint1: points[1], controlPoint2: points[2])
            }
        }
    
        override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) {
            finishPath()
        }
    
        override func touchesCancelled(_ touches: Set<UITouch>?, with event: UIEvent?) {
            finishPath()
        }
    
        private func finishPath() {
            constructIncrementalImage()
            path = nil
            setNeedsDisplay()
        }
    
        private func createPathStarting(at point: CGPoint) -> UIBezierPath {
            let localPath = UIBezierPath()
    
            localPath.move(to: point)
    
            localPath.lineWidth = lineWidth
            localPath.lineCapStyle = .round
            localPath.lineJoinStyle = .round
    
            return localPath
        }
    
        private func constructIncrementalImage() {
            UIGraphicsBeginImageContextWithOptions(bounds.size, false, 0.0)
            strokeColor.setStroke()
            snapshotImage?.draw(at: .zero)
            path?.stroke()
            temporaryPath?.stroke()
            snapshotImage = UIGraphicsGetImageFromCurrentImageContext()
            UIGraphicsEndImageContext()
        }
    }
    

    You could even marry this with iOS 9 predictive touches (as I described in my other answer), which could reduce lag even further.

  2. To take this resulting image and use it elsewhere, you can just grab the incrementalImage (which I renamed to snapshotImage, above), and drop it into an image view of the other view.

For Swift 2 rendition, see previous revision of this answer.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...