CGContext 在 iOS 中 — 是什么、方法及如何管理绘图上下文

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

CGContext — Core Graphics 中用于在 iOS 和 macOS 上执行 2D 绘图命令的主要对象。它代表一个图形上下文,存储当前状态:填充颜色、线条粗细、变换、裁剪区域和字体。所有绘图操作——线条、矩形、文本、图像——都通过 CGContext 执行。根据 Apple Developer Documentation, 2026,CGContext 是 Apple 生态系统中所有栅格和矢量图形的基础。

要点

  • CGContext — 图形上下文,所有 Core Graphics 2D 命令通过它执行。
  • setStrokeColorsetFillColor 管理上下文中线条和填充的颜色。
  • addPathdrawPath 允许绘制任意 CGPath 轮廓。
  • clip(to:) 设置裁剪区域以限制在特定区域内绘制。
  • saveGStaterestoreGState 保存和恢复上下文的状态堆栈。

什么是 CGContext?

CGContext — Core Graphics 框架中的一个不透明类型(opaque type),代表执行 2D 渲染命令的环境。它管理状态堆栈、当前色彩空间、坐标变换和裁剪区域。CGContext 是所有 Quartz 2D 绘图函数交互的唯一对象——任何图形操作都通过它进行。

CGContext 根据用途有多种变体:bitmap context 用于在内存中处理栅格图像,PDF context 用于生成 PDF 文档,window context 用于屏幕渲染(由 UIView 在 draw(_:) 中提供)。在 iOS 中,开发者最常使用从 UIGraphicsGetCurrentContext() 在 draw(_:) 方法中获取的上下文,或从 UIGraphicsBeginImageContextWithOptions 获取的离线渲染上下文。

上下文通过状态堆栈应用所有设置——开发者可以保存当前状态(saveGState),修改参数,然后恢复之前的状态(restoreGState)。这在绘制复杂场景时至关重要,其中不同元素需要不同的颜色、线条粗细和变换。根据 Apple WWDC 2023,正确使用状态堆栈可以提高代码可读性并防止视觉伪影。

状态管理:saveGState 和 restoreGState

CGContextSaveGState 将当前上下文状态的副本压入堆栈。此状态包括所有设置:颜色、线条粗细、变换、阴影、裁剪区域、字体。修改参数后,调用 CGContextRestoreGState 将上下文恢复到保存的状态,撤销所有中间更改。在 iOS 上,堆栈最多可包含 32 个状态。

堆栈的实际应用

在绘制具有不同样式的一组元素时(例如图表的彩色扇区),状态堆栈消除了手动记住和恢复每个参数的需要。如果没有 saveGState/restoreGState,开发者必须在每个元素之后重新设置颜色、阴影和变换。使用堆栈可以简化代码并避免恢复错误。

保持保存和恢复的平衡很重要——每个 saveGState 必须有一个配对的 restoreGState。破坏平衡会导致状态泄漏或过早恢复,从而引起不可预测的显示。Xcode 静态分析器不检查 CGContext 堆栈平衡,因此正确性的责任在于开发者。建议将配对分组到 do/finally 块中。

swift
override func draw(_ rect: CGRect) {
    guard let ctx = UIGraphicsGetCurrentContext() else { return }

    ctx.saveGState()
    ctx.setStrokeColor(UIColor.red.cgColor)
    ctx.setLineWidth(3)
    ctx.addRect(CGRect(x: 50, y: 50, width: 200, height: 100))
    ctx.strokePath()
    ctx.restoreGState()
}

颜色和填充

CGContextSetFillColorWithColor 设置后续操作的填充颜色。颜色作为 CGColorRef 传递,包含有关色彩空间和颜色分量的信息。CGContextSetStrokeColorWithColor 类似地用于描边颜色。Apple 建议缓存经常使用的 CGColorRef 以提高性能。

CGContext 中的渐变

CGContextDrawLinearGradient 在两点之间绘制带有指定颜色的线性渐变。CGContextDrawRadialGradient 创建从一个圆过渡到另一个圆的径向渐变。使用渐变时,首先创建一个包含颜色数组和停止位置的 CGGradientRef 对象。CGContext 中的渐变支持 RGB 和 Grayscale 色彩空间。

用渐变填充区域需要预先设置裁剪区域。如果没有 clip,渐变将填充整个上下文,而不仅仅是所需的形状。典型模式:将路径添加到上下文,调用 clip 限制区域,然后绘制渐变。此模式用于所有需要复杂形状渐变填充的应用程序中,从按钮到背景。

函数用途色彩空间
setFillColor设置填充颜色任意(通过 CGColor)
setStrokeColor设置描边颜色任意(通过 CGColor)
drawLinearGradient绘制线性渐变RGB 或 Grayscale
drawRadialGradient绘制径向渐变RGB 或 Grayscale

绘制轮廓和线条

CGContextAddPath 将先前创建的 CGPathRef 添加到上下文以供后续绘制。添加路径后,可以描边(strokePath)、填充(fillPath)或执行两种操作。CGContextBeginPath 开始新路径,清除之前的路径。上下文在任意时刻只支持一个活动路径。

直线和逐点曲线

CGContextMoveToPoint 将路径的当前点移动到指定坐标。CGContextAddLineToPoint 从当前点到指定点绘制一条直线。CGContextAddCurveToPoint 添加三次贝塞尔曲线,CGContextAddQuadCurveToPoint 添加二次曲线。CGContextClosePath 用一条直线连接最后一点和第一点来闭合路径。

与 UIBezierPath 不同的是,所有这些方法在路径对象上调用,而在 CGContext 中它们直接在上下文上调用。这种区别在选择方法时很重要:UIBezierPath 更适合重用轮廓,CGContext 更适合以最小开销进行一次性绘制。CGContext 还通过 CGContextSetLineDash 支持虚线,通过 CGContextSetLineCap 支持灵活端点,通过 CGContextSetLineJoin 支持连接。

swift
let ctx = UIGraphicsGetCurrentContext()!
ctx.beginPath()
ctx.moveTo(x: 50, y: 100)
ctx.addLineTo(x: 200, y: 100)
ctx.addCurveTo(x1: 250, y1: 50, x2: 300, y2: 150, x3: 350, y3: 100)
ctx.setStrokeColor(UIColor.blue.cgColor)
ctx.setLineWidth(5)
ctx.strokePath()

裁剪区域和变换

CGContextClip 将绘制区域限制在当前轮廓内。所有后续绘制操作仅在此区域内可见。这是遮罩、圆角和非标准元素边界的基本机制。裁剪区域也通过 saveGState/restoreGState 保存和恢复。

坐标变换

CGContextTranslateCTM 平移上下文坐标原点。CGContextScaleCTM 缩放 X 和 Y 轴。CGContextRotateCTM 围绕当前原点旋转坐标系。变换是累积的——每个后续变换都相对于当前坐标系应用。复合变换(平移 + 旋转 + 缩放)用于动画和元素定位。

裁剪和变换的结合使用提供了强大的功能:可以通过裁剪创建复杂的遮罩,然后对整个内容应用变换。典型示例——以一定角度显示带有圆角的图像部分。操作顺序很重要:首先设置裁剪区域,然后应用变换,最后绘制内容。Apple 建议在绘制循环中尽量减少 CTM(Current Transformation Matrix)的更改次数。

swift
override func draw(_ rect: CGRect) {
    guard let ctx = UIGraphicsGetCurrentContext() else { return }
    ctx.saveGState()

    // 裁剪区域 - 圆形
    ctx.addEllipse(in: CGRect(x: 50, y: 50, width: 200, height: 200))
    ctx.clip()

    // 内容变换
    ctx.translateBy(x: 150, y: 150)
    ctx.rotate(by: .pi / 4)
    ctx.translateBy(x: -150, y: -150)

    UIImage(named: "photo")?.draw(at: CGPoint(x: 50, y: 50))
    ctx.restoreGState()
}

CGContext 绘图示例

让我们看一个使用 CGContext 创建渐变图表的完整示例。该图表包括三个不同颜色的扇区,带有渐变填充和文本标签。代码演示了在单个上下文中 saveGState、clip、渐变和文本渲染的组合。

swift
class PieChartView: UIView {

    private struct Slice {
        let color: UIColor
        let value: CGFloat
        let label: String
    }

    override func draw(_ rect: CGRect) {
        guard let ctx = UIGraphicsGetCurrentContext() else { return }

        let slices = [
            Slice(color: .systemRed, value: 0.4, label: "iOS"),
            Slice(color: .systemGreen, value: 0.35, label: "Android"),
            Slice(color: .systemBlue, value: 0.25, label: "Other")
        ]

        let center = CGPoint(x: bounds.midX, y: bounds.midY)
        let radius = bounds.width * 0.35
        var startAngle: CGFloat = -.pi / 2

        for slice in slices {
            let endAngle = startAngle + .pi * 2 * slice.value

            ctx.saveGState()
            ctx.moveTo(x: center.x, y: center.y)
            ctx.addArc(center: center, radius: radius,
                       startAngle: startAngle, endAngle: endAngle, clockwise: false)
            ctx.closePath()
            ctx.setFillColor(slice.color.cgColor)
            ctx.fillPath()

            // 文本标签
            let midAngle = startAngle + endAngle / 2
            let labelPos = CGPoint(
                x: center.x + radius * 0.7 * cos(midAngle),
                y: center.y + radius * 0.7 * sin(midAngle)
            )
            slice.label.draw(at: labelPos, withAttributes: [
                .foregroundColor: UIColor.white,
                .font: UIFont.boldSystemFont(ofSize: 14)
            ])
            ctx.restoreGState()
            startAngle = endAngle
        }
    }
}

在此示例中,图表的每个扇区在自己的 saveGState/restoreGState 块中绘制。这种方法确保一个扇区的颜色和路径设置不会影响其他扇区。addArc 方法从起始角度到结束角度创建弧线,closePath 将扇区闭合到中心。文本标签使用三角函数 cos 和 sin 计算位置,放置在扇区角度的中线上。

为提高频繁重绘时的性能,建议将角度和位置的计算缓存到单独的数组中。CGContext 在 CPU 上执行所有操作,因此具有数十个扇区的复杂场景可能需要优化——例如在 CGLayer 中预栅格化或使用 Metal 进行 GPU 加速。对于大多数具有 5-10 个元素的 UI 任务,CGContext 的性能仍然可以接受。

常见问题

如何在 iOS 中获取当前 CGContext?

在 UIView 的 draw(_:) 方法内部或 UIGraphicsBeginImageContextWithOptions 块内部调用 UIGraphicsGetCurrentContext()。对于 CALayer 图层,使用 CALayerDelegate 代理的 draw(in:),它会接收一个准备好的上下文。

fillPath 和 strokePath 有什么区别?

fillPath 用 setFillColor 的颜色填充当前轮廓的内部区域。strokePath 使用 setStrokeColor、setLineWidth 和 setLineCap 参数沿轮廓绘制线条。两种操作顺序执行:先 fill,必要时再 stroke。

什么是位图 CGContext?

Bitmap CGContext — 通过 CGBitmapContextCreate 创建的上下文,它绘制到内存缓冲区而不是屏幕。用于后台图像生成、无显示图形处理以及为 Metal 或 OpenGL 创建纹理。

如何重置 CGContext 的所有设置?

Core Graphics 不提供重置所有设置的方法。在更改前使用 saveGState,然后使用 restoreGState 返回到原始状态。或者,可以使用相同参数创建新的上下文。

为什么渐变没有显示在形状内部?

在调用 drawLinearGradient 之前,通过 clip 在所需轮廓上设置裁剪区域。没有 clip,渐变会填充整个上下文。顺序:addPath → clip → drawLinearGradient — 确保渐变在形状内部。

总结

  • CGContext — Core Graphics 中用于 iOS 和 macOS 上所有 2D 绘图操作的核心对象。
  • saveGStaterestoreGState 管理上下文状态堆栈,防止副作用。
  • setFillColorsetStrokeColor 通过 CGColorRef 设置填充和描边颜色。
  • addPathstrokePathfillPath 处理任意 CGPath 轮廓。
  • clip 将绘制区域限制在当前轮廓内,用于遮罩和圆角。
  • translateCTMscaleCTMrotateCTM 变换上下文坐标系。
  • 所有 CGContext 操作在 CPU 上执行——复杂图形请使用 CGLayer 或 Metal。

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

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

讨论项目

另请阅读