技术求助:如何计算UIBezierPath中addQuadCurve的控制点?
嗨,Roi!我明白你现在的需求:四个可拖拽的UIView,连接它们的中心形成框形,还要在角落做出平滑的曲线过渡,但卡在了addQuadCurve(to:controlPoint:)的控制点计算上。我来给你一步步拆解这个问题,帮你搞定它。
首先,先明确二次贝塞尔曲线的核心逻辑:它由起点、终点、控制点三个点决定,曲线会向控制点的方向弯曲,而且在起点和终点处的切线方向分别指向控制点、从控制点指向终点——这个特性是我们计算控制点的关键。
假设你的四个UIView中心已经按顺时针顺序排好了:p1(左上)、p2(右上)、p3(右下)、p4(左下)。我们要在每个角落(比如p2)用二次贝塞尔曲线替换原来的直角,实现平滑过渡。
步骤1:定义基础参数
首先确定一个偏移距离r——这个值决定了曲线的“弯曲程度”,可以是固定值(比如20pt),也可以根据UIView的大小动态调整(比如取View宽度的1/2,让曲线刚好贴合View边缘)。
然后计算每个边的方向向量并单位化(单位向量能保证我们的偏移距离是固定的):
// 计算各边的向量(从前一个点到后一个点) let v1 = p2 - p1 // p1→p2的向量 let v2 = p3 - p2 // p2→p3的向量 let v3 = p4 - p3 // p3→p4的向量 let v4 = p1 - p4 // p4→p1的向量 // 单位化向量(方向不变,长度变为1) let v1Unit = v1.normalized() let v2Unit = v2.normalized() let v3Unit = v3.normalized() let v4Unit = v4.normalized()
步骤2:计算曲线的起点和终点
对于每个角落,我们需要在相邻的两条边上各取一个点,作为曲线的起点和终点:
- 比如在
p2这个角落,我们在p1→p2的线段上取一个靠近p2的点B,距离p2为r; - 再在
p2→p3的线段上取一个靠近p2的点C,距离p2为r;
代码示例:
// p2角落的曲线起点(在p1→p2线段上) let pointB = p2 - v1Unit * r // p2角落的曲线终点(在p2→p3线段上) let pointC = p2 + v2Unit * r
步骤3:计算控制点
根据二次贝塞尔的切线特性:
- 曲线在
pointB处的切线方向要和p1→p2的方向一致,所以控制点必须在pointB沿着p1→p2的延长线上; - 曲线在
pointC处的切线方向要和p2→p3的方向一致,所以控制点必须在pointC沿着p2→p3的反方向延长线上;
联立这两个条件,你会发现控制点就是角落的那个中心p2!是不是很简单?
验证一下:当p1在(0,0)、p2在(100,0)、p3在(100,100),r=20时:
pointB是(80,0),pointC是(100,20),控制点是(100,0);- 这条曲线会从(80,0)向右上弯曲到(100,20),完美实现
p2处的平滑过渡。
步骤4:构建完整的闭合路径
把所有角落的曲线连起来,形成完整的框形路径:
let path = UIBezierPath() // 处理p1→p2的直线段,从靠近p1的点走到靠近p2的点 let pointA = p1 + v1Unit * r path.move(to: pointA) path.addLine(to: pointB) // p2处的曲线 path.addQuadCurve(to: pointC, controlPoint: p2) // 处理p2→p3的直线段 let pointD = p3 - v2Unit * r path.addLine(to: pointD) // p3处的曲线 let pointE = p3 + v3Unit * r path.addQuadCurve(to: pointE, controlPoint: p3) // 处理p3→p4的直线段 let pointF = p4 - v3Unit * r path.addLine(to: pointF) // p4处的曲线 let pointG = p4 + v4Unit * r path.addQuadCurve(to: pointG, controlPoint: p4) // 处理p4→p1的直线段 let pointH = p1 - v4Unit * r path.addLine(to: pointH) // 闭合路径,处理p1处的曲线 path.addQuadCurve(to: pointA, controlPoint: p1) path.close()
进阶:近似圆弧的曲线
如果你的需求是更接近标准圆角(圆弧)的效果,那控制点就不能直接用角落的中心了,需要做一点调整。对于角度为θ的角落,控制点的计算可以用这个公式:
// 计算角平分线方向的单位向量 let bisectorUnit = (v1Unit + v2Unit).normalized() // 计算控制点偏移量,这个系数是二次贝塞尔近似圆弧的经验值 let offset = r * (4/3) * tan(θ/4) // 控制点坐标 let controlPoint = p2 + bisectorUnit * offset
这里的θ是两条边的夹角,可以通过向量的点积计算:θ = acos(v1Unit.dot(v2Unit))。
这样得到的曲线会非常接近真实的圆弧,视觉效果更统一。
希望这些方法能帮你搞定曲线效果!如果还有细节问题,随时补充~
内容的提问来源于stack exchange,提问作者Roi Mulia

