UIBezierPath:是什么、绘图方法及工作原理

作者: IT Sectr 发布日期: 2026-07-21 阅读时间: 8 分钟

UIBezierPath — 来自 UIKit 的一个类,封装了用于 iOS 中 2D 图形的矢量路径。它将轮廓的几何描述与绘图功能相结合,允许创建线条、贝塞尔曲线、弧线和矩形。与 CGPath 不同,UIBezierPath 包含自己的绘图方法,可直接在当前图形上下文中使用。根据 Apple Developer Documentation, 2026,UIBezierPath 支持面向对象封装中的所有标准 Quartz 2D 操作。

要点

  • UIBezierPath — CGPath 的面向对象封装,用于在 UIKit 中处理矢量轮廓。
  • move(to:)addLine(to:) 定义路径的直线段。
  • addQuadCurve(to:controlPoint:)addCurve(to:controlPoint1:controlPoint2:) 构建贝塞尔曲线。
  • addArc(withCenter:radius:startAngle:endAngle:clockwise:) 创建弧线和扇形。
  • fillstroke — 无需使用额外对象即可在当前上下文中直接绘制路径。

什么是 UIBezierPath?

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 中,至今仍是创建圆形容器的标准。

swift
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二次贝塞尔12
addCurve三次贝塞尔23
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:)。

swift
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 属性的动画,以实现从起点到终点 “绘制” 线条的效果。

swift
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。

swift
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 有何不同?

UIBezierPath — CGPath 的面向对象封装,包含内置的 fill() 和 stroke() 方法。CGPath 是一个不可修改的 Core Graphics C 对象,需要通过 CGContext 进行显式绘制。UIBezierPath 对于 UIKit 更方便,CGPath 对于重复使用更高效。

如何只圆角矩形的上角?

使用 init(roundedRect:byRoundingCorners:cornerRadii:) 并传入 .topLeft 和 .topRight 参数给 roundedRect。这是唯一一个允许选择性圆角而无需创建复杂复合路径的 UIBezierPath 方法。

如何以不同样式重复使用 UIBezierPath?

cgPath 属性返回一个不可变的 CGPathRef,可以传递给 CAShapeLayer 或与不同颜色和粗细一起使用。UIBezierPath 的修改(移动点)不会影响之前获取的 cgPath,直到显式更新。

为什么 fill() 不能填充整个路径?

检查 usesEvenOddFillRule — 如果轮廓有自相交,even-odd 规则可能会留下未填充的区域。设置 usesEvenOddFillRule = false 以获得标准的非零缠绕填充规则。

如何为 UIBezierPath 的形状添加动画?

在 CAShapeLayer 上使用键路径为 “path” 的 CABasicAnimation。路径的两个状态必须包含相同数量的段和控制点 — 否则动画将是卡顿的或无法工作。

总结

  • UIBezierPath — 面向对象的 UIKit 类,用于处理矢量轮廓和 2D 图形。
  • move(to:)addLine(to:) — 构建直线和多边形的基本方法。
  • addQuadCurveaddCurve 创建带一个和两个控制点的二次和三次贝塞尔曲线。
  • addArc 绘制圆弧,灵活控制角度和方向。
  • fill()stroke() 直接在当前图形上下文中执行绘制。
  • init(roundedRect:)init(ovalIn:) 初始化器加速了标准形状的创建。
  • cgPath 属性允许 UIBezierPath 与 Core Animation 和 CAShapeLayer 集成以实现动画。

我们将开发一款交钥匙移动应用程序

IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。

讨论项目

另请阅读