【问题标题】:Removing lagging latency in drawing UIBezierPath smooth lines in Swift消除在 Swift 中绘制 UIBezierPath 平滑线时的滞后延迟
【发布时间】:2016-01-25 15:08:03
【问题描述】:

下面的代码通过覆盖触摸绘制平滑曲线,但存在明显的滞后或延迟。该代码使用addCurveToPoint 并在每4 个触摸点后调用setNeedsDisplay,这会导致出现跳跃的外观,因为绘图跟不上手指的移动。为了消除滞后或感知延迟,可以用addQuadCurveToPointaddLineToPoint 临时填充接触点1、2、3(直到接触点4)。

  1. 如何在代码中实际实现这一点,以在显示最终曲线之前使用临时 Line 和 QuadCurved 线来消除感知滞后?

  2. 如果下面的类附加到一个UIView(例如viewOne 或self),我如何在touchesEnded 之后将绘图复制到该类之外的另一个UIView(例如viewTwo)?

     //  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.
        }
    
    
    }
    

【问题讨论】:

    标签: ios swift drawing uibezierpath


    【解决方案1】:
    1. 是的,每隔几个点添加一条曲线会导致延迟。所以,是的,您可以通过向points[1] 添加一条线、向points[2] 添加一条四边形曲线以及向points[3] 添加一条三次曲线来减少这种影响。

      正如您所说,请确保将其添加到单独的路径中。所以,在 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()
          }
      }
      

      您甚至可以将此与 iOS 9 预测性触摸结合起来(正如我所描述的 in my other answer),这可以进一步减少延迟。

    2. 1234563

    对于 Swift 2 版本,请参阅之前的 revision of this answer

    【讨论】:

    • 非常感谢。杰出的!感谢您的清晰解释。我会尝试抓住snapshotImage。我注意到代码中有两个人工制品。 1 - 绘图时,抬起手指,然后再次开始绘图,之前绘图的最后一部分似乎被移除并替换为细线。这只有时会发生。图片:i.imgur.com/LIpfF4q.png 2 - 绘图时出现一个平角。这也只是有时发生。图片:i.imgur.com/QyWpXG0.png 是什么导致了这些人工制品?
    • 好收获。是的,问题是您仅在调用 drawRect 时才配置属性,但 constructIncrementalImage 没有。但是,UIBezierPath 的配置根本不属于drawRect。它确实应该在路径实例化时设置。因此,我编写了一个方法来实例化和配置路径,并在需要创建路径时使用它。请参阅上面的修订答案。
    • 我注意到另一个关于连续绘图期间累积滞后延迟的问题,您的见解可能有助于解决 - stackoverflow.com/questions/35067811
    • 您能否简要解释一下pointCount == 5 时上面的代码部分发生了什么,以及它与pointCount == 3 的比较,两者都使用addCurveToPoint 四倍贝塞尔曲线?
    • 谢谢。我喜欢简化代码的想法,但我注意到自从更改后,有时在绘图结束时会出现一条双线,图片:i.imgur.com/vU96UT5.png 要尝试追踪问题,我设置了 lineWidth: CGFloat = 1 和将 print(points.count) 添加到 touchesEnded 的开头。当 points.count = 2 双线出现时,结果是,但我不知道为什么要比较前面的代码。 (它不会发生在 3 和 4 上。)关于它可能是什么的任何想法?
    猜你喜欢
    • 1970-01-01
    • 2012-06-09
    • 1970-01-01
    • 2020-10-11
    • 2016-10-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-09-05
    相关资源
    最近更新 更多