关于ios:在Swift中连续绘制UIBezierPath期间消除滞后延迟

Removing lagging latency during continuous period of drawing UIBezierPath in Swift

下面的代码通过覆盖触摸来绘制线条,但是在连续不间断的绘制期间开始出现滞后。手指在屏幕上移动的时间越长,这种滞后就会越积越多。结果是实际设备上的 CPU 几乎达到最大值(CPU 98% ),并且绘制的时间越长,生成的图像就越不稳定。

此外,当画得特别快时,尤其是在圆圈中,在 pathtemporaryPath(或 localPath)之间绘制的路径存在差异。尽管它们是在不同时间绘制的,但它们似乎同时出现在屏幕上,这在视觉上分散了两条快速绘制的路径。在下面的图像之一中,内部路径 (path) 似乎与外部路径 (temporaryPath) 相距一段距离。

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

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

3 - 如何更改 pathtemporaryPath 的 alpha/opacity?

enter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
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()
}

}


你问过:

  • How can the lagging latency over a period of continuous drawing be eliminated?
  • 正如您正确推测的那样,是的,创建快照并重置路径可以通过限制路径的长度来解决此问题。

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

  • How can the discrepancy in the paths drawn be eliminated?
  • 这是因为快照包含临时路径。但是该临时路径的全部目的是随着更多点的进入,它将被丢弃。因此,您不应将其包含在您创建的中间手势快照中。

    所以,我建议在快照函数中添加一个参数,指示是否应包含 temporaryPath。当在手势中调用它时,您将指定 includeTemporaryPathfalse,但在手势结束时调用它时,includeTemporaryPath 将是 true.

    例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    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()
        }
    }

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

    然后你问:

  • How can the alpha/opacity of the path and temporaryPath be changed?
  • 您可以使用适当的 alpha 调整调用 setStroke 时使用的颜色。例如,如果您希望临时路径位于主路径 alpha 的一半,您可以执行以下操作:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    override func drawRect(rect: CGRect) {
        snapshotImage?.drawInRect(rect)

        strokeColor.setStroke()
        path?.stroke()

        strokeColor.colorWithAlphaComponent(0.5).setStroke()
        temporaryPath?.stroke()
    }