You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

技术求助:如何计算UIBezierPath中addQuadCurve的控制点?

解决UIBezierPath二次贝塞尔曲线控制点计算问题

嗨,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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 09:04:29