iOS Swift中如何通过控制点调整UIBezierPath绘制图形的大小?
需求:通过控制点调整UIBezierPath绘制图形的大小
已实现以下功能:
- 用
UIBezierPath绘制圆形、直线、自由线等图形 - 图形选中与拖拽移动效果
需要新增功能:通过func drawPoints(bounds:CGRect)中的topRight和bottomRight控制点,调整圆形和直线的大小。
现有代码如下:
import UIKit enum ShapeType { case circle, rectangle, line, pencil } class DrawingView: UIView { private var path = UIBezierPath() private var startPoint: CGPoint? private var shapeType: ShapeType? private var strokeColor = #colorLiteral(red: 0.2274509804, green: 0.7098039216, blue: 0.2862745098, alpha: 1) private var lineWidth: CGFloat = 3.0 private var paths: [(path: UIBezierPath, color: UIColor)] = [] private var selectedPathIndex: Int? private var offset = CGPoint.zero override init(frame: CGRect) { super.init(frame: frame) setupView() } required init?(coder aDecoder: NSCoder) { super.init(coder: aDecoder) setupView() } private func setupView() { backgroundColor = .clear isMultipleTouchEnabled = false } override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { guard let touch = touches.first else { return } startPoint = touch.location(in: self) print("selectPath ==> \(point)") selectedPathIndex = nil setNeedsDisplay() for (index, path) in paths.enumerated() { if path.path.bounds.contains(startPoint ?? CGPoint()){ selectedPathIndex = index self.path = path.path print(offset) offset = CGPoint(x: (startPoint?.x ?? 0.0) - path.path.bounds.origin.x, y: (startPoint?.y ?? 0) - path.path.bounds.origin.y) print(offset) print("did touch index", selectedPathIndex) setNeedsDisplay() return } } path = UIBezierPath() if shapeType == .pencil { path.move(to: startPoint!) } } override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) { guard let touch = touches.first, let shapeType = shapeType else { return } let currentPoint = touch.location(in: self) print(currentPoint) if let index = selectedPathIndex { let newOrigin = CGPoint(x: currentPoint.x - offset.x, y: currentPoint.y - offset.y) print(newOrigin) let translation = CGAffineTransform(translationX: newOrigin.x - path.bounds.origin.x, y: newOrigin.y - path.bounds.origin.y) print(translation) self.paths[index].path.apply(translation) setNeedsDisplay() }else{ if shapeType == .pencil { if selectedPathIndex != nil{ path.move(to: currentPoint) }else{ path.addLine(to: currentPoint) } setNeedsDisplay() } else { path.removeAllPoints() guard let startPoint = startPoint else { return } switch shapeType { case .line: path.move(to: startPoint) path.addLine(to: currentPoint) case .rectangle: path = UIBezierPath(rect: CGRect(origin: startPoint, size: CGSize(width: currentPoint.x - startPoint.x, height: currentPoint.y - startPoint.y))) case .circle: let radius = hypot(currentPoint.x - startPoint.x, currentPoint.y - startPoint.y) path = UIBezierPath(arcCenter: startPoint, radius: radius, startAngle: 0, endAngle: 2 * .pi, clockwise: true) case .pencil: break } setNeedsDisplay() } } } override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { guard let shapeType = shapeType else { return } if self.selectedPathIndex == nil{ if shapeType != .pencil { paths.append((path.copy() as! UIBezierPath, strokeColor)) } else { paths.append((path, strokeColor)) } } setNeedsDisplay() } override func draw(_ rect: CGRect) { for pathData in paths { pathData.color.setStroke() pathData.path.lineWidth = lineWidth pathData.path.stroke() } strokeColor.setStroke() path.lineWidth = lineWidth path.stroke() if selectedPathIndex != nil{ for (index, pathData) in paths.enumerated() { pathData.color.setStroke() pathData.path.lineWidth = lineWidth pathData.path.stroke() if let selectedIndex = selectedPathIndex, selectedIndex == index { let bounds = path.bounds // Draw a rectangle around the selected path if isPencil(path: path) { print("This path is a line.") self.drawSquare(bounds: bounds) } else if isCurve(path: path) { print("This path is a curve.") self.drawSquare(bounds: bounds) self.drawPoints(bounds: bounds) } else if isLine(path: path){ self.drawPoints(bounds: bounds) } } } } } func drawSquare(bounds:CGRect){ let selectionRect = UIBezierPath(rect: bounds) // Create a rectangle slightly larger than the path #colorLiteral(red: 0.1490196078, green: 0.5019607843, blue: 0.9215686275, alpha: 1).setStroke() selectionRect.lineWidth = 1 // Set the line width for the rectangle selectionRect.stroke() // Draw the rectangle } func drawPoints(bounds:CGRect){ let topRight = CGPoint(x: bounds.maxX, y: bounds.minY) let bottomRight = CGPoint(x: bounds.minX, y: bounds.maxY) self.drawPoint(at: topRight, color: #colorLiteral(red: 0.1490196078, green: 0.5019607843, blue: 0.9215686275, alpha: 1)) // Change color and size as needed self.drawPoint(at: bottomRight, color: #colorLiteral(red: 0.1490196078, green: 0.5019607843, blue: 0.9215686275, alpha: 1)) } func drawPoint(at point: CGPoint, color: UIColor) { let pointSize: CGFloat = 16 let pointRect = CGRect(x: point.x - pointSize / 2, y: point.y - pointSize / 2, width: pointSize, height: pointSize) let pointPath = UIBezierPath(ovalIn: pointRect) color.setFill() UIColor.white.setStroke() pointPath.lineWidth = 5 pointPath.stroke() color.setStroke() pointPath.fill() } func clear() { path.removeAllPoints() paths.removeAll() setNeedsDisplay() } func setStrokeColor(_ color: UIColor) { strokeColor = color } func setLineWidth(_ width: CGFloat) { lineWidth = width } func setShapeType(_ shape: ShapeType) { shapeType = shape } func undo() { if !paths.isEmpty { path.removeAllPoints() paths.removeLast() self.setNeedsDisplay() } } func isLine(path: UIBezierPath) -> Bool { var isLine = true path.cgPath.applyWithBlock { element in let type = element.pointee.type if type == .addCurveToPoint || type == .addQuadCurveToPoint { isLine = false } } return isLine } func isPencil(path: UIBezierPath) -> Bool { var isLine = true var isLineCount = 0 path.cgPath.applyWithBlock { element in let type = element.pointee.type isLineCount += 1 if type == .addCurveToPoint || type == .addQuadCurveToPoint { isLine = false } } return isLine && isLineCount > 2 } func isCurve(path: UIBezierPath) -> Bool { var isCurve = false path.cgPath.applyWithBlock { element in let type = element.pointee.type if type == .addCurveToPoint || type == .addQuadCurveToPoint { isCurve = true } } return isCurve } }
实现方案
1. 重构数据存储结构
直接存储UIBezierPath无法逆向获取图形的原始参数(比如直线的端点、圆形的圆心),需要新增结构体保存图形的完整信息:
// 新增枚举标记控制点类型 enum ControlPointType { case topRight, bottomRight } // 新增结构体存储图形完整数据 struct ShapeData { let type: ShapeType var parameters: [CGPoint] // 存储图形原始参数:直线存[起点,终点],圆形存[圆心,半径参考点] var path: UIBezierPath let color: UIColor } // 替换原paths数组 private var shapes: [ShapeData] = [] // 新增变量记录当前拖拽的控制点 private var draggingControlPoint: ControlPointType? private var originalShape: ShapeData?
2. 修改touchesBegan逻辑
优先判断是否点击了控制点,再处理图形选中:
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) { guard let touch = touches.first else { return } let touchPoint = touch.location(in: self) // 检查是否点击控制点 draggingControlPoint = nil if let selectedIndex = selectedPathIndex { let shape = shapes[selectedIndex] let bounds = shape.path.bounds let topRight = CGPoint(x: bounds.maxX, y: bounds.minY) let bottomRight = CGPoint(x: bounds.minX, y: bounds.maxY) let pointSize: CGFloat = 16 // 判断点击区域是否在控制点范围内 let topRightRect = CGRect(x: topRight.x - pointSize/2, y: topRight.y - pointSize/2, width: pointSize, height: pointSize) let bottomRightRect = CGRect(x: bottomRight.x - pointSize/2, y: bottomRight.y - pointSize/2, width: pointSize, height: pointSize) if topRightRect.contains(touchPoint) { draggingControlPoint = .topRight originalShape = shape return } else if bottomRightRect.contains(touchPoint) { draggingControlPoint = .bottomRight originalShape = shape return } } // 原图形选中逻辑,替换paths为shapes selectedPathIndex = nil for (index, shape) in shapes.enumerated() { if shape.path.bounds.contains(touchPoint) { selectedPathIndex = index originalShape = shape offset = CGPoint(x: touchPoint.x - shape.path.bounds.origin.x, y: touchPoint.y - shape.path.bounds.origin.y) setNeedsDisplay() return } } // 原绘制新图形逻辑 path = UIBezierPath() if shapeType == .pencil { path.move(to: touchPoint) } startPoint = touchPoint }
3. 修改touchesMoved逻辑
区分拖拽图形和拖拽控制点的处理逻辑:
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) { guard let touch = touches.first else { return } let currentPoint = touch.location(in: self) // 处理控制点拖拽 if let controlPoint = draggingControlPoint, let index = selectedPathIndex, let originShape = originalShape { var updatedPath: UIBezierPath? var updatedParams = originShape.parameters switch originShape.type { case .line: // 直线:控制点对应端点,拖拽时更新端点坐标 switch controlPoint { case .topRight: updatedParams[1] = currentPoint case .bottomRight: updatedParams[0] = currentPoint } updatedPath = UIBezierPath() updatedPath?.move(to: updatedParams[0]) updatedPath?.addLine(to: updatedParams[1]) case .circle: // 圆形:拖拽控制点更新半径(保持正圆) let center = originShape.parameters[0] let newRadius = hypot(currentPoint.x - center.x, currentPoint.y - center.y) updatedPath = UIBezierPath(arcCenter: center, radius: newRadius, startAngle: 0, endAngle: 2 * .pi, clockwise: true) updatedParams[1] = currentPoint default: break } if let path = updatedPath { var newShape = originShape newShape.path = path newShape.parameters = updatedParams shapes[index] = newShape setNeedsDisplay() } return } // 处理图形拖拽移动逻辑,替换paths为shapes if let index = selectedPathIndex, let originShape = originalShape { let newOrigin = CGPoint(x: currentPoint.x - offset.x, y: currentPoint.y - offset.y) let translation = CGAffineTransform(translationX: newOrigin.x - originShape.path.bounds.origin.x, y: newOrigin.y - originShape.path.bounds.origin.y) var updatedShape = originShape updatedShape.path.apply(translation) // 同步平移图形参数 updatedShape.parameters = updatedShape.parameters.map { $0.applying(translation) } shapes[index] = updatedShape setNeedsDisplay() return } // 原绘制新图形逻辑,替换paths为shapes guard let shapeType = shapeType, let startPoint = startPoint else { return } if shapeType == .pencil { path.addLine(to: currentPoint) setNeedsDisplay() } else { path.removeAllPoints() var params: [CGPoint] = [] switch shapeType { case .line: path.move(to: startPoint) path.addLine(to: currentPoint) params = [startPoint, currentPoint] case .rectangle: path = UIBezierPath(rect: CGRect(origin: startPoint, size: CGSize(width: currentPoint.x - startPoint.x, height: currentPoint.y - startPoint.y))) params = [startPoint, currentPoint] case .circle: let radius = hypot(currentPoint.x - startPoint.x, currentPoint.y - startPoint.y) path = UIBezierPath(arcCenter: startPoint, radius: radius, startAngle: 0, endAngle: 2 * .pi, clockwise: true) params = [startPoint, currentPoint] case .pencil: break } setNeedsDisplay() } }
4. 修改touchesEnded逻辑
保存新图形时使用ShapeData结构体:
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent?) { guard let shapeType = shapeType else { return } if selectedPathIndex == nil && draggingControlPoint == nil { var params: [CGPoint] = [] switch shapeType { case .line: guard let start = startPoint, let end = path.currentPoint else { return } params = [start, end] case .rectangle: guard let start = startPoint else { return } let end = CGPoint(x: start.x + path.bounds.width, y: start.y + path.bounds.height) params = [start, end] case .circle: guard let center = startPoint else { return } let radiusPoint = CGPoint(x: center.x + path.bounds.width/2, y: center.y) params = [center, radiusPoint] case .pencil: break } let newShape = ShapeData(type: shapeType, parameters: params, path: path.copy() as! UIBezierPath, color: strokeColor)
相关产品推荐
相关产品推荐

