如何修复贝塞尔曲线绘制代码以实现从起点开始的平滑曲线
解决UIPanGesture绘制二次贝塞尔曲线初始阶段变直线的问题
嘿,这个问题我之前做手绘功能时也踩过坑!二次贝塞尔曲线本身需要起点、控制点、终点三个独立的点才能形成弯曲的形态,刚拖拽的前几个点因为数量太少,很容易出现控制点和端点重叠的情况,自然就画出直线了。咱们来一步步解决它:
问题根源
二次贝塞尔曲线的绘制逻辑依赖三个不重合的点:当你刚开始拖拽,只有1-2个触摸点时,计算出的中点(也就是你用的控制点)会和前后触摸点几乎重叠,曲线公式退化成直线方程,结果自然就是直线而非曲线了。
解决方案
我们可以通过积累足够触摸点再切换曲线绘制,或者添加距离判断避免重叠这两种方式来解决,下面给你具体的代码修改方案:
方案1:积累触摸点,分阶段绘制
先维护一个数组存储所有触摸点,初始阶段(少于3个点)先用直线过渡,点足够后再切换成二次曲线,这样能保证控制点有效:
// 先定义存储触摸点的数组 private var touchPoints = [CGPoint]() @objc private func pan(gesture: UIPanGestureRecognizer) { let currentPoint = gesture.location(in: self) switch gesture.state { case .began: // 拖拽开始时清空历史点,添加第一个点 touchPoints.removeAll() touchPoints.append(currentPoint) case .changed: touchPoints.append(currentPoint) // 创建绘制路径 let drawPath = UIBezierPath() drawPath.move(to: touchPoints.first!) drawPath.lineWidth = 2.0 // 根据点的数量选择绘制方式 for i in 1..<touchPoints.count { let prevPoint = touchPoints[i-1] let currPoint = touchPoints[i] if touchPoints.count < 3 { // 前两个点之间先画直线 drawPath.addLine(to: currPoint) } else { // 点足够时,用上一段的中点作为控制点,保证曲线平滑 let prevMidpoint = calculateMidpoint(point1: touchPoints[i-2], point2: prevPoint) drawPath.addQuadCurve(to: currPoint, controlPoint: prevMidpoint) } } // 渲染路径(这里用CAShapeLayer举例,你可以换成自己的渲染方式) let shapeLayer = CAShapeLayer() shapeLayer.path = drawPath.cgPath shapeLayer.strokeColor = UIColor.black.cgColor shapeLayer.fillColor = UIColor.clear.cgColor shapeLayer.lineWidth = 2.0 self.layer.addSublayer(shapeLayer) case .ended, .cancelled: // 拖拽结束清空点数组 touchPoints.removeAll() default: break } } // 中点计算函数 private func calculateMidpoint(point1: CGPoint, point2: CGPoint) -> CGPoint { return CGPoint(x: (point1.x + point2.x)/2, y: (point1.y + point2.y)/2) }
方案2:添加距离阈值判断
如果你不想分阶段,也可以直接判断控制点和前后点的距离,当距离过小(几乎重叠)时画直线,否则画曲线:
// 假设你原本维护了previousPoint变量存储上一个触摸点 @objc private func pan(gesture: UIPanGestureRecognizer) { let currentPoint = gesture.location(in: self) switch gesture.state { case .began: previousPoint = currentPoint case .changed: let midpoint = calculateMidpoint(point1: previousPoint, point2: currentPoint) // 判断中点和前点的距离,小于阈值则画直线 let distance = hypot(midpoint.x - previousPoint.x, midpoint.y - previousPoint.y) let path = UIBezierPath() path.move(to: previousPoint) if distance < 1.0 { path.addLine(to: currentPoint) } else { path.addQuadCurve(to: currentPoint, controlPoint: midpoint) } // 渲染路径(替换成你自己的渲染逻辑) let shapeLayer = CAShapeLayer() shapeLayer.path = path.cgPath shapeLayer.strokeColor = UIColor.black.cgColor shapeLayer.fillColor = UIColor.clear.cgColor shapeLayer.lineWidth = 2.0 self.layer.addSublayer(shapeLayer) previousPoint = currentPoint default: break } } // 中点计算函数 private func calculateMidpoint(point1: CGPoint, point2: CGPoint) -> CGPoint { return CGPoint(x: (point1.x + point2.x)/2, y: (point1.y + point2.y)/2) }
这样就能避免初始阶段因为点重叠导致的直线问题,同时保证后续的曲线绘制流畅平滑。
内容的提问来源于stack exchange,提问作者Tometoyou




