2016-01-28 43 views
3

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

另外,特别是在圈出特别快时,在pathtemporaryPath(或localPath)之间绘制的路径存在差异。尽管它们在不同的时间被绘制出来,但它们似乎同时出现在屏幕上,看起来两条路径很快就会分散注意力。内部路径(path)似乎与外部路径(temporaryPath)之间的距离远远高于下图之一中以红色突出显示的距离。

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

2 - 如何消除路径中的差异?

3 - 如何更改pathtemporaryPath的alpha /不透明度?

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

} 
+0

你说“我已经试过缓存绘图时pointCount == 4后约50个连续绘图点“。那么,也许你应该向我们展示那些代码,因为这正是解决这个问题的方法。但是也许50个人太少(因为当你谈论手势时,触动很快就会架起来,尤其是在使用合并触摸时)。但拍摄快照是典型的解决方案(意识到快照过程本身很慢,所以您需要平衡快照频率与路径长度)。 – Rob

+0

@Rob是的,一些更新的代码会有帮助。我已经更新了,对此表示遗憾。我也更新了问题。代码中添加了一个变量'counterPoints'。我还将'autoreleasepool'添加到'drawRect'以帮助避免类崩溃。不确定这是否有用或不必要,但我注意到它可以帮助有时避免CPU崩溃。我在iOS7以及iOS9上进行实验时,没有使用合并触摸。谢谢。 – user4806509

回答

1

你问:

  1. 如何可以在一段连续拉拔的滞后延迟被淘汰?

正如你正确地推测,是的,做一个快照和复位路径可以通过限制路径将持续多久解决这个问题。

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

  1. 如何消除路径中的差异?

这是因为快照包含临时路径引起的。但是这个临时路径的全部目的是随着更多点的进入,它将被丢弃。所以你不应该把它包含在你创建中间手势的快照中。

所以,我会建议添加一个参数快照功能,它表明是否应该包括temporaryPath。当将其称为中间手势时,您将指定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() 
    } 
} 

顺便说一句,虽然我是谁提供的路径生成代码的人,我意识到它可以简化一点。我也修正了一个错误。见上面的代码。

您接着问:

  • 如何路径和temporaryPath的α/不透明度改变?
  • 你可以只调整与调用setStroke用适当的字母的颜色。例如,如果你想临时路径是在主路径的alpha的一半,你可以这样做:

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

    谢谢。我喜欢简化的代码压缩的想法!但我注意到,自改变以来,在绘制结束时有时会出现双线,图像:http://i.imgur.com/vU96UT5.png 要试着找出问题,我设置了'lineWidth :CGFloat = 1'并将'print(points.count)'添加到'touchesEnded'的开头。当'points.count = 2'时双触线出现时触摸起来,但我无法弄清楚为什么会比较以前的代码。 (它不会发生在'3'和'4'上。)关于它可能是什么的任何想法? – user4806509

    +0

    我注意到上面的代码中有一个artefact。我发布的细节:http://stackoverflow.com/questions/35608766 – user4806509