使用多个UIBezierPaths绘制性能

时间:2015-04-14 01:19:29

标签: xcode swift drawing core-graphics uibezierpath

我有一个绘图应用程序,它当前由一个主视图控制器组成,它包含4个独立的UIViews,它们同时复制在触摸的象限上绘制的线与另一个3相反,其中一些轴反转以使绘图对称。 当使用这种方法时,绘图是平滑的,你可以看到当用户移动他们的手指时,有很多点被收集,因为线条跟随他们的移动很好。

高级代码如下所示:

MainViewController.swift

override func touchesBegan(touches: NSSet, withEvent event: UIEvent) {
    var touch: UITouch = touches.anyObject() as UITouch
    var p = CGPoint()

    if touch.view == quadrant1 {
        p = touch.locationInView(quadrant1)
        quadrant1.began(p)
        var p2 = CGPointMake(quadrant2.bounds.width - p.x, p.y)
        quadrant2.began(p2)
        var p3 = CGPointMake(p.x,quadrant3.bounds.height - p.y)
        quadrant3.began(p3)
        var p4 = CGPointMake(quadrant4.bounds.width - p.x, quadrant4.bounds.height - p.y)
        quadrant4.began(p4)
    } else if touch.view == quadrant2 {
    ...

触动'感动'并且'结束了'通过进行相同的计算,在每个象限中调用类似的方法。 Quadrant文​​件如下所示:

Quadrant1,2,3,4.swift

// A counter to determine if there are enough points to make a quadcurve
var ctr = 0

// The path to stroke
var path = UIBezierPath()

// After the user lifts their finger and the line has been finished the same line is rendered to an image and the UIBezierPath is cleared to prevent performance degradation when lots of lines are on screen
var incrementalImage = UIImage()

// This array stores the points that make each line
var pts: [CGPoint] = []

override func drawRect(rect: CGRect) {
        incrementalImage.drawInRect(rect)
        path.stroke()
    }

func began (beganPoint: CGPoint) {
    ctr = 0
    var p = beganPoint
    pts.insert(beganPoint, atIndex: 0)
}

func moved(movedPoints: CGPoint) {
        var p = movedPoints
        ctr++
        pts.insert(movedPoints, atIndex: ctr)
        // This IF statement handles the quadcurve calculations
        if ctr == 3 {
            pts[2] = CGPointMake((pts[1].x + pts[3].x)/2.0, (pts[1].y + pts[3].y)/2.0);
            path.moveToPoint(pts[0])
            path.addQuadCurveToPoint(pts[2], controlPoint: pts[1])
            self.setNeedsDisplay()
            pts[0] = pts[2]
            pts[1] = pts[3]
            ctr = 1
        }
    }

    func ended (endPoint: CGPoint) {
        if ctr == 2 {
            path.moveToPoint(pts[0])
            path.addQuadCurveToPoint(pts[2], controlPoint: pts[1])
        }
        self.drawBitmap()
        self.setNeedsDisplay()
        path.removeAllPoints()
    }

    func drawBitmap() {
        UIGraphicsBeginImageContextWithOptions(self.bounds.size, false, 0.0)
        var rectPath = UIBezierPath(rect: self.bounds)
        UIColor.clearColor().setFill()
        rectPath.fill()
        incrementalImage.drawAtPoint(CGPointZero)
        color.setStroke()
        path.stroke()
        incrementalImage = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
}

所以上面的方法实际上工作得非常好并且产生了相当流畅的线条,但是用户总是被锁定使用4个象限,因为它们是分开的UIView's:

enter image description here


经过一番思考,我们决定废弃4个独立的UIView并使用单个视图来处理绘图,这样可以一次绘制任意数量的线,为用户提供更多选项(8行为例如),这就是事情变得棘手的地方。

MainViewController不再处理触摸交互方法,新的' DrawingView'使用UILongPressGestureRecogniser捕获手势本身。

func handleLongPressDrawing(sender: UILongPressGestureRecognizer) {
    var p = sender.locationInView(self)

    switch sender.state {
    case UIGestureRecognizerState.Began:
        self.began(p)
        break;
    case UIGestureRecognizerState.Changed:
        self.moved(p)
        break;
    case UIGestureRecognizerState.Ended:
        self.ended(p)
    default:
        break;
    }
}

现在,这些方法引用了一个新的DrawingElement类来执行对称性计算:

enum GridType {
    case ONE, TWO_1, TWO_2, TWO_3, TWO_4, THREE, FOUR_1, FOUR_2, FIVE, SIX_1, SIX_2, SEVEN, EIGHT_1, SIXTEEN
}

enum DrawingElementType {
    case PATH, POINT, CIRCLE
}

class DrawingElement: NSObject {

var points : [CGPoint] = []
private var drawingWidth : CGFloat!
private var drawingHeight : CGFloat!
private var gridType : GridType!
private var drawingElementType : DrawingElementType!

init(gridType : GridType, drawingWidth : CGFloat, drawingHeight : CGFloat) {
    self.gridType = gridType
    self.drawingWidth = drawingWidth
    self.drawingHeight = drawingHeight
    super.init()
}

func getPoints() -> [CGPoint] {
    return points
}

func addPoint(pointCG: CGPoint) {
    points.append(pointCG)
}

func getPoint(pos : Int) -> CGPoint {
    return points[pos]
}

func getDrawingWidth() -> CGFloat {
    return drawingWidth
}

func setDrawingWidth(w : CGFloat) {
    drawingWidth = w
}

func getDrawingWidthCG() -> CGFloat {
    return CGFloat(drawingWidth)
}

func getDrawingHeight() -> CGFloat {
    return drawingHeight
}

func setDrawingHeight(h : CGFloat) {
    drawingHeight = h
}

func getDrawingHeightCG() -> CGFloat {
    return CGFloat(drawingHeight)
}

func getPointCount() -> Int {
    return points.count
}

func getDrawingElementType() -> DrawingElementType {
    return drawingElementType
}

func setDrawingElementType(det : DrawingElementType) {
    drawingElementType = det
}

func getGridType() -> GridType {
    return gridType
}

func setGridType(gt : GridType) {
    gridType = gt
}

func smoothLinesPart1() {
    points[2] = CGPointMake((points[1].x + points[3].x)/2.0, (points[1].y + points[3].y)/2.0)
}

func smoothLinesMoveTo() -> CGPoint {
    return points[0]
}

func smoothLinesQuadCurve() -> (CGPoint, CGPoint) {
    return (points[2], points[1])
}

func smoothLinesReorderArray() {
    points[0] = points[2]
    points[1] = points[3]
}

func getCalculatedPoints(allPoints : [CGPoint]) -> [Int : [CGPoint]] {
    var newPoints = [CGPoint]()
    var numberOfPoints : Int!
    var temp : CGFloat!
    var x : CGFloat!
    var y : CGFloat!
    //println("Before Path points: \(allPoints)")
    var pathPoints = [Int() : [CGPoint]()]
        if(gridType == GridType.EIGHT_1) {
        numberOfPoints = 8
    } else if(gridType == GridType.ONE) {
        numberOfPoints = 1
    } else if(gridType == GridType.TWO_1) {
        numberOfPoints = 2
    } else if(gridType == GridType.FOUR_1) {
        numberOfPoints = 4
    }

    var firstTime = true
    for point in allPoints {
        x = point.x
        y = point.y

        if(gridType == GridType.EIGHT_1 || gridType == GridType.ONE || gridType == GridType.TWO_1 || gridType == GridType.FOUR_1) {

            if(firstTime) {
                for i in 1...numberOfPoints {
                    switch (i) {
                    case 5:
                        temp = y;
                        y = x;
                        x = temp;
                        pathPoints[4] = [CGPoint(x: x, y: y)]
                    case 1:

                        pathPoints[0] = [CGPoint(x: x, y: y)]
                        //println(" first point\(pathPoints[0])")
                        break;
                    case 2:
                        pathPoints[1] = [CGPoint(x: (x - getDrawingWidthCG()) * -1, y: y)]
                        break;
                    case 6:
                        pathPoints[5] = [CGPoint(x: (x - getDrawingWidthCG()) * -1, y: y)]
                        break;
                    case 3:
                        pathPoints[2] = [CGPoint(x: x, y: (y - getDrawingHeightCG()) * -1)]
                        break;
                    case 7:
                        pathPoints[6] = [CGPoint(x: x, y: (y - getDrawingHeightCG()) * -1)]
                        break;
                    case 4:
                        pathPoints[3] = [CGPoint(x: (x - getDrawingWidthCG()) * -1, y: (y - getDrawingHeightCG()) * -1)]
                        break;
                    case 8:
                        pathPoints[7] = [CGPoint(x: (x - getDrawingWidthCG()) * -1, y: (y - getDrawingHeightCG()) * -1)]
                        break;
                    default:
                        break
                        //newPoints.append(CGPoint(x: x, y: y))
                    }

                }
                firstTime = false
            } else {

                for i in 1...numberOfPoints {
                    switch (i) {
                    case 5:
                        temp = y;
                        y = x;
                        x = temp;
                        pathPoints[4]?.append(CGPoint(x: x, y: y))
                    case 1:

                        pathPoints[0]?.append(CGPoint(x: x, y: y))
                        //println(" first point\(pathPoints[0])")
                        break;
                    case 2:
                        pathPoints[1]?.append(CGPoint(x: (x - getDrawingWidthCG()) * -1, y: y))
                        break;
                    case 6:
                        pathPoints[5]?.append(CGPoint(x: (x - getDrawingWidthCG()) * -1, y: y))
                        break;
                    case 3:
                        pathPoints[2]?.append(CGPoint(x: x, y: (y - getDrawingHeightCG()) * -1))
                        break;
                    case 7:
                        pathPoints[6]?.append(CGPoint(x: x, y: (y - getDrawingHeightCG()) * -1))
                        break;
                    case 4:
                        pathPoints[3]?.append(CGPoint(x: (x - getDrawingWidthCG()) * -1, y: (y - getDrawingHeightCG()) * -1))
                        break;
                    case 8:
                        pathPoints[7]?.append(CGPoint(x: (x - getDrawingWidthCG()) * -1, y: (y - getDrawingHeightCG()) * -1))
                        break;
                    default:
                        break
                        //newPoints.append(CGPoint(x: x, y: y))
                    }

                }
            }
        }

    }
}

这是在DrawingViews交互处理程序的各个部分调用的:

var paths = [Int() : UIBezierPath()]

func began (beganPoint: CGPoint) {
    strokes = 0
    var p = beganPoint
        ctr = 0
        //pts.insert(beganPoint, atIndex: 0)
    drawingElement?.addPoint(beganPoint)
}

func moved(movedPoints: CGPoint) {
    strokes++
    var p = movedPoints
            ctr++
    drawingElement?.addPoint(movedPoints)
    if ctr == 3 {
        drawingElement?.smoothLinesPart1()
        path.moveToPoint(drawingElement!.smoothLinesMoveTo())
        path.addQuadCurveToPoint(drawingElement!.smoothLinesQuadCurve().0, controlPoint: drawingElement!.smoothLinesQuadCurve().1)
        self.setNeedsDisplay()
        drawingElement?.smoothLinesReorderArray()
        ctr = 1

    }

    var pointsArray : [CGPoint] = drawingElement!.getPoints()

    var calcArray = drawingElement?.getCalculatedPoints(pointsArray)

    let sortedCalcArray = sorted(calcArray!) { $0.0 < $1.0 }

    if pointsArray.count > 1 {
        for (pIndex, path) in sortedCalcArray {
            paths[pIndex] = UIBezierPath()
            for var i = 0; i < path.count; i++ {
                paths[pIndex]!.moveToPoint(path[i])
                if(i > 0) {
                    paths[pIndex]!.addLineToPoint(path[i-1])
                }
                self.setNeedsDisplay()
            }
        }
    }

override func drawRect(rect: CGRect) {

    for (index, path) in paths {

        path.lineCapStyle = kCGLineCapRound
        path.lineWidth = lineWidth

        color.setStroke()
        path.stroke()
    }

    color.setStroke()
    incrementalImage.drawInRect(rect)
}

}

我有一种感觉:1)iPhone确实喜欢一次在一个视图中绘制4条或更多路径,或者2)由于每次用户移动它们时运行的循环次数,性能会降低手指。以下是使用上述新代码的类似行:

enter image description here

所以我想知道是否有人能够阐明为什么新代码如此不同或者更好的方法可能是什么。

由于

1 个答案:

答案 0 :(得分:0)

所以在经过一些试验和错误之后,我保留了上面的大部分代码,唯一值得注意的区别是我构建了4个独立的UIBezierPaths并消除了嵌套在另一个for循环中的for循环。这似乎导致了这个问题