【发布时间】:2015-04-14 01:19:28
【问题描述】:
我有一个绘图应用程序,它目前由一个主视图控制器组成,它拥有 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 {
...
通过执行相同的计算,触摸“已移动”和“已结束”在每个象限中调用类似的方法。象限文件如下所示:
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:
经过一番思考,我们决定废弃 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) 性能下降,因为每次用户移动它们时运行的循环数手指。以下是上述新代码的类似行:
因此,我想知道是否有人能够阐明为什么新代码的绘制方式如此不同,或者有什么更好的方法。
谢谢
【问题讨论】:
-
您是否尝试过在 UIView 子类directly 中捕获触摸,而不是使用手势识别器?我怀疑手势识别器可能不会发送尽可能多的事件,因为它并不是真正用于跟踪,而是用于长按。
-
附带说明,仅绘制一条路径然后对每个象限应用适当的变换可能对性能很有用。这样,您可以使用单个 UIView,例如几个 CAShapeLayer 子层(每个子层都经过转换,但使用相同的路径)。然后,您还可以根据它们位于视图的哪个部分对触摸应用类似的转换。
-
Henri 这两点都很好,我会先切换到 touches 事件,看看有什么不同,然后看看 CAShapeLayer 的想法,谢谢!
标签: xcode swift drawing core-graphics uibezierpath