UIBezierPath — 来自 UIKit 的一个类,封装了用于 iOS 中 2D 图形的矢量路径。它将轮廓的几何描述与绘图功能相结合,允许创建线条、贝塞尔曲线、弧线和矩形。与 CGPath 不同,UIBezierPath 包含自己的绘图方法,可直接在当前图形上下文中使用。根据 Apple Developer Documentation, 2026,UIBezierPath 支持面向对象封装中的所有标准 Quartz 2D 操作。
要点
UIBezierPath — UIKit 框架的一个类,继承自 NSObject 并实现了 NSCopying、NSSecureCoding。它提供了用于构建和绘制矢量轮廓的高级 Objective-C/Swift API。在内部,UIBezierPath 存储了对 CGPathRef(Core Graphics 对象)的引用,但添加了用于绘图、几何修改和样式管理的便捷方法。
UIBezierPath 相较于直接使用 CGPath 的主要优势在于内置的绘图方法。在 UIBezierPath 实例上调用 fill() 或 stroke() 会自动配置上下文并绘制路径。该类还支持 lineWidth、lineCapStyle、lineJoinStyle、miterLimit 和 flatness 属性,无需单独的对象来存储样式即可管理外观。
UIBezierPath 在大多数 iOS 应用程序中用于 UIView 内的自定义绘制:从简单的图标到复杂的动画图形。UIView 类的 draw(_:) 方法是开发人员创建和绘制 UIBezierPath 的主要场所。该类与 Core Animation 完全兼容,可用于通过 CAShapeLayer 和 CABasicAnimation 进行路径动画。
move(to:) 方法将路径的起始点移动到指定坐标而不进行绘制。这是在构建任何线段之前必须进行的调用。addLine(to:) 从当前位置到指定点添加一条直线段。要闭合轮廓,使用 close() 方法,它从最后一个点绘制一条线到最后一个 move(to:) 的第一个点。
UIBezierPath 为现成形状提供了几个便捷的初始化器。init(rect:) 创建一个矩形,init(roundedRect:cornerRadius:) — 一个带有圆角的矩形。UIRectCorner 参数允许仅圆角选定的角,例如 topLeft 和 bottomRight,而将其余部分保持为直角。这广泛用于创建自定义按钮和卡片。
init(ovalIn:) 创建一个内切于给定矩形的椭圆。如果矩形是正方形,椭圆将变成圆形。init(roundedRect:byRoundingCorners:cornerRadii:) — 最灵活的变体,允许为每个角设置不同的半径。这个初始化器出现在 iOS 6 中,至今仍是创建圆形容器的标准。
let path = UIBezierPath()
path.move(to: CGPoint(x: 50, y: 50))
path.addLine(to: CGPoint(x: 200, y: 50))
path.addLine(to: CGPoint(x: 200, y: 150))
path.addLine(to: CGPoint(x: 125, y: 200))
path.addLine(to: CGPoint(x: 50, y: 150))
path.close()
addQuadCurve(to:controlPoint:) 添加一条带一个控制点的二次贝塞尔曲线。这种曲线类型用于简单的平滑弯曲,当需要最小化计算量时。二次曲线总是抛物线,并且保证位于由起点、控制点和终点形成的三角形内。
addCurve(to:controlPoint1:controlPoint2:) 添加一条带两个控制点的三次贝塞尔曲线。三次曲线用于所有现代矢量编辑器和 TrueType 字体中。两个控制点为精确控制曲线形状提供了更多自由度。此方法建议用于构建有机和复杂的轮廓。
在构建曲线时,遵循连续性原则很重要 — 曲线在段连接点处的平滑度。对于 C1 连续性(切线),下一条曲线的控制点必须与前一条曲线的控制点位于同一条直线上。对于 C2 连续性(曲率),需要更复杂的协调,这在 CAShapeLayer 中会在具有相同数量控制点的两个 UIBezierPath 之间进行动画时自动确保。
| 方法 | 类型 | 控制点数量 | 阶数 |
|---|---|---|---|
| addQuadCurve | 二次贝塞尔 | 1 | 2 |
| addCurve | 三次贝塞尔 | 2 | 3 |
| addArc | 弧线 | — | — |
addArc(withCenter:radius:startAngle:endAngle:clockwise:) 添加一条具有给定参数的圆弧。角度以相对于圆心的弧度指定。零角度对应于向右方向(表盘上的 3 点钟)。clockwise 参数确定方向:true — 顺时针,false — 逆时针。弧线对于饼图、速度表和交互式进度环是不可或缺的。
对于一个完整的圆,startAngle = 0 和 endAngle = .pi * 2 就足够了。要创建扇形(作为饼图的一部分),使用 addArc 后跟 addLine(to:) 到圆心和 close()。UIBezierPath 没有内置的 addCircle 方法 — 圆是通过 init(ovalIn:) 使用正方形矩形或通过 addArc 使用完整角度创建的。
addArc(withCenter:radius:startAngle:endAngle:clockwise:) 方法会自动执行 move(to:) 到弧线的起始点,如果当前位置与其不重合。如果当前位置不同,则在当前位置和弧线起点之间绘制一条直线。这种行为与使用 Core Graphics 不同,在 Core Graphics 中,每个新轮廓之前都需要显式的 move(to:)。
let center = CGPoint(x: 150, y: 150)
let slice = UIBezierPath()
slice.move(to: center)
slice.addArc(
withCenter: center,
radius: 100,
startAngle: 0,
endAngle: .pi * 0.75,
clockwise: true
)
slice.close()
fill() 使用当前的 fillColor 颜色或通过 setFill() 在图形上下文中设置的颜色填充路径的内部区域。对于自相交的路径,填充规则由 usesEvenOddFillRule 属性确定。如果为 true,则应用 even-odd 规则:如果从该点发出的射线与轮廓相交奇数次,则该点被视为在形状内部。
stroke() 使用当前的 lineWidth、lineCapStyle 和 lineJoinStyle 设置绘制路径的轮廓。lineCapStyle 确定开放段端点的形状:.butt(平切)、.round(半圆形)、.square(带突出部的方形)。lineJoinStyle 确定连接角的形状:.miter(尖角)、.round(圆角)、.bevel(切角)。
对于虚线,使用 setLineDash(_:count:phase:) 属性。它接受交替虚线和空格长度的数组、元素数量和起始相位。虚线的可视化在路径的光栅化阶段进行 — 复杂的图案不会增加轮廓的几何复杂性。CAShapeLayer 支持 strokeEnd 属性的动画,以实现从起点到终点 “绘制” 线条的效果。
override func draw(_ rect: CGRect) {
let path = UIBezierPath(ovalIn: CGRect(x: 50, y: 50, width: 200, height: 200))
UIColor.blue.setFill()
UIColor.black.setStroke()
path.lineWidth = 4
path.fill()
path.stroke()
}
让我们使用 UIBezierPath 创建一个环形自定义进度指示器。环的构建由两个半径相同但描边颜色不同的弧线组成。第一个弧线 — 一个完整的灰色圆(背景),第二个 — 显示进度的段。动画使用 CAShapeLayer 和 CABasicAnimation。
class RingView: UIView {
private let progressLayer = CAShapeLayer()
override func layoutSubviews() {
super.layoutSubviews()
let center = CGPoint(x: bounds.midX, y: bounds.midY)
let radius = bounds.width * 0.4
let lineWidth: CGFloat = 12
let backgroundRing = UIBezierPath()
backgroundRing.addArc(withCenter: center, radius: radius,
startAngle: -.pi / 2, endAngle: .pi * 1.5, clockwise: true)
progressLayer.path = backgroundRing.cgPath
progressLayer.strokeColor = UIColor.systemBlue.cgColor
progressLayer.lineWidth = lineWidth
progressLayer.lineCap = .round
progressLayer.fillColor = UIColor.clear.cgColor
progressLayer.strokeEnd = 0
layer.addSublayer(progressLayer)
}
func animateProgress(to value: CGFloat) {
CABasicAnimation(keyPath: "strokeEnd").apply {
$0.toValue = value
$0.duration = 1.5
$0.timingFunction = CAMediaTimingFunction(name: .easeOut)
progressLayer.add($0, forKey: nil)
}
progressLayer.strokeEnd = value
}
}
在此示例中,UIBezierPath 用于创建一个圆形轮廓,然后通过 cgPath 属性传递给 CAShapeLayer。strokeEnd 从 0 到 1 的动画可视化环的填充。这种方法用于加载指示器、健身追踪器和进度小部件中。layoutSubviews 方法确保在 View 尺寸更改时重建路径。
lineCap = .round 为弧线添加圆形末端,使指示器看起来更整洁。strokeEnd 属性是 CAShapeLayer 的一个可动画属性,不需要在每一帧重新绘制整个轮廓。动画完全在 GPU 上通过 Core Animation 执行,确保 60 FPS 且不占用 CPU。
常见问题
UIBezierPath — CGPath 的面向对象封装,包含内置的 fill() 和 stroke() 方法。CGPath 是一个不可修改的 Core Graphics C 对象,需要通过 CGContext 进行显式绘制。UIBezierPath 对于 UIKit 更方便,CGPath 对于重复使用更高效。
使用 init(roundedRect:byRoundingCorners:cornerRadii:) 并传入 .topLeft 和 .topRight 参数给 roundedRect。这是唯一一个允许选择性圆角而无需创建复杂复合路径的 UIBezierPath 方法。
cgPath 属性返回一个不可变的 CGPathRef,可以传递给 CAShapeLayer 或与不同颜色和粗细一起使用。UIBezierPath 的修改(移动点)不会影响之前获取的 cgPath,直到显式更新。
检查 usesEvenOddFillRule — 如果轮廓有自相交,even-odd 规则可能会留下未填充的区域。设置 usesEvenOddFillRule = false 以获得标准的非零缠绕填充规则。
在 CAShapeLayer 上使用键路径为 “path” 的 CABasicAnimation。路径的两个状态必须包含相同数量的段和控制点 — 否则动画将是卡顿的或无法工作。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。