Swift中的人工制品绘图

use*_*509 3 drawing ios uibezierpath swift

下面的代码通过覆盖触摸来绘制线条,但是在绘制时仍然存在伪像,如下图所示。

当在屏幕上进行锯齿形绘制时改变方向时,有时线会变成平坦的直角,而不是保持圆形。当在小圆圈上现场绘制时,也会出现伪影,当手指离开屏幕时,绘制点会闪烁半个圆圈,有时会留下半个圆圈和部分圆圈残留物。

伪影是断断续续的,不是完全一致或可预测的,这使得很难在代码中找到问题。它同时存在于模拟器和iOS7-iOS9的设备中。

包含两个绘制点和线的视频屏幕截图以及Xcode项目的zip文件以名为Archive.zip(23MB)https://www.dropbox.com/s/hm39rdiuk0mf578/Archive.zip?dl的文件上传到DropBox。= 0

问题:

1-在代码中,造成此点/半圆伪像的原因是什么?如何纠正?

在此处输入图片说明

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
        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()
    }
}
Run Code Online (Sandbox Code Playgroud)

Rob*_*Rob 6

这似乎是一个令人着迷的错误,addQuadCurveToPoint并且addCurveToPoint如果控制点与两个端点在同一行上,则它不适合lineJoinStyle。因此,您可以对此进行测试(通过查看atan2各个要点,并确保不相同),如果是这样,请addLineToPoint改为执行以下操作:

我发现此修改后的代码删除了这些工件:

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

        addCubicCurveToPath(path)

        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])
        addQuadCurveToPath(temporaryPath)
    } else if pointCount == 4 {
        temporaryPath = createPathStartingAtPoint(points[0])
        addCubicCurveToPath(temporaryPath)
    }
}

/// Add cubic curve to path
///
/// Because of bug with bezier curves that fold back on themselves do no honor `lineJoinStyle`,
/// check to see if this occurs, and if so, just add lines rather than cubic bezier path.

private func addCubicCurveToPath(somePath: UIBezierPath?) {
    let m01 = atan2(points[0].x - points[1].x, points[0].y - points[1].y)
    let m23 = atan2(points[2].x - points[3].x, points[2].y - points[3].y)
    let m03 = atan2(points[0].x - points[3].x, points[0].y - points[3].y)
    if m01 == m03 || m23 == m03 || points[0] == points[3] {
        somePath?.addLineToPoint(points[1])
        somePath?.addLineToPoint(points[2])
        somePath?.addLineToPoint(points[3])
    } else {
        somePath?.addCurveToPoint(points[3], controlPoint1: points[1], controlPoint2: points[2])
    }
}

/// Add quadratic curve to path
///
/// Because of bug with bezier curves that fold back on themselves do no honor `lineJoinStyle`,
/// check to see if this occurs, and if so, just add lines rather than quadratic bezier path.

private func addQuadCurveToPath(somePath: UIBezierPath?) {
    let m01 = atan2(points[0].x - points[1].x, points[0].y - points[1].y)
    let m12 = atan2(points[1].x - points[2].x, points[1].y - points[2].y)
    let m02 = atan2(points[0].x - points[2].x, points[0].y - points[2].y)
    if m01 == m02 || m12 == m02 || points[0] == points[2] {
        somePath?.addLineToPoint(points[1])
        somePath?.addLineToPoint(points[2])
    } else {
        somePath?.addQuadCurveToPoint(points[2], controlPoint: points[1])
    }
}
Run Code Online (Sandbox Code Playgroud)

同样,这可能过于谨慎,但要确保两个连续的点在guard声明中永远不会相同是谨慎的:

override func touchesMoved(touches: Set<UITouch>, withEvent event: UIEvent?) {
    let touch: AnyObject? = touches.first
    let point = touch!.locationInView(self)

    guard point != points.last else { return }

    points.append(point)
    totalPointCount = totalPointCount + 1

    updatePaths()

    if totalPointCount > 50 {
        constructIncrementalImage(includeTemporaryPath: false)
        path = nil
        totalPointCount = 0
    }

    setNeedsDisplay()
}
Run Code Online (Sandbox Code Playgroud)

如果您发现其他存在问题的情况,则可以重复我刚才所做的调试练习。即,运行代码,直到出现问题为止,但立即停止并查看points数组日志以查看是什么原因导致了问题,然后创建一个init?(coder:)始终100%始终再现问题的代码,例如:

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)

    points.append(CGPoint(x: 239.33332824707, y: 419.0))
    points.append(CGPoint(x: 239.33332824707, y: 420.0))
    points.append(CGPoint(x: 239.33332824707, y: 419.3))

    updatePaths()
}
Run Code Online (Sandbox Code Playgroud)

然后,由于始终存在可复制的问题,因此调试很容易。因此,诊断出问题之后,我进行了修改,updatePaths直到问题解决为止。然后,我注释掉init?并重复了整个练习。