2016-01-28 44 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