You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何修复贝塞尔曲线绘制代码以实现从起点开始的平滑曲线

解决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

火山引擎 最新活动