在Swift中绘制的人工制品

时间:2016-02-24 17:17:14

标签: ios swift drawing uibezierpath

下面的代码通过覆盖触摸来绘制线条,但是在绘制时会有一个人工制品,如下图所示。

当在横跨屏幕的锯齿形绘图时改变方向时,有时线条会变成平直的角落,而不是保持圆形。当以小圆圈在现场绘制时,人工制品也会出现,当手指离开屏幕时,绘图点会闪烁半圈,有时会留下半圈和部分圆圈残留。

人工制品是间歇性的,并不是完全一致或可预测的模式,因此很难在代码中找到问题。它存在于iOS7中的模拟器和设备上 - iOS9。

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

问题:

1 - 在代码中,是什么导致了这个点/半圆的假象以及如何纠正?

enter image description here

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

1 个答案:

答案 0 :(得分:3)

这似乎是addQuadCurveToPointaddCurveToPoint中一个引人入胜的错误,如果控制点与两个终点在同一条线上,它就不会荣誉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])
    }
}

此外,这可能过于谨慎,但确保两个连续点与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()
}

如果您发现存在问题的其他情况,您可以重复我刚刚进行的调试练习。即,运行代码直到问题出现,但立即停止并查看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()
}

然后,由于一致可重现的问题,调试很容易。因此,在诊断出问题之后,我修改了updatePaths,直到问题得到解决。然后我评论了init?并重复了整个练习。