1. 引言
在 iOS 开发中,绘图是一个绕不开的话题。无论是自定义控件、图表展示、还是游戏渲染,高效的绘图能力直接影响应用的流畅度和用户体验。本文将从 iOS 绘图的技术栈出发,系统梳理 Core Graphics、UIKit 绘图、Core Animation 以及 Metal 等方案的适用场景与性能特点,帮助你在不同需求下做出合理的技术选型。
2. iOS 绘图技术栈概览
iOS 平台提供了多层次的绘图 API,从高层到低层依次为:
- UIKit / SwiftUI:面向日常 UI 绘制,封装度高,适合大多数业务场景。
- Core Graphics(Quartz 2D):基于 CPU 的 2D 绘图引擎,提供路径、渐变、阴影等能力。
- Core Animation:基于 GPU 的图层合成与动画框架,适合高频刷新场景。
- Metal:面向底层的 GPU 编程接口,适合复杂渲染与高性能需求。
选择哪一层,取决于你的性能要求、绘制频率以及开发成本。
3. Core Graphics 基础与性能要点
Core Graphics 是 iOS 绘图的基础,几乎所有自定义绘制最终都会落到它上面。
3.1 基本绘制流程
swift
import UIKit
class CustomView: UIView {
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
context.setFillColor(UIColor.systemBlue.cgColor)
context.fill(rect)
}
}
3.2 性能优化建议
- 避免频繁调用
draw(_:):draw(_:)是 CPU 绘制,频繁触发会带来性能压力。尽量使用setNeedsDisplay()合并多次更新。 - 使用
draw(_:)的替代方案 :对于静态内容,优先使用CALayer的contents或shouldRasterize属性进行缓存。 - 减少离屏渲染:阴影、圆角、遮罩等效果容易触发离屏渲染,应谨慎使用。
4. Core Animation 与 GPU 加速
当绘制内容需要高频更新(如动画、滚动)时,Core Animation 是更优的选择。
4.1 图层绘制
swift
let layer = CALayer()
layer.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
layer.backgroundColor = UIColor.systemRed.cgColor
layer.cornerRadius = 10
view.layer.addSublayer(layer)
4.2 性能要点
- 尽量使用图层而非
draw(_:):图层由 GPU 合成,性能远高于 CPU 绘制。 - 善用
shouldRasterize:对复杂图层启用栅格化,可缓存渲染结果,减少重复绘制。 - 避免隐式动画 :频繁修改图层属性会触发隐式动画,必要时使用
CATransaction关闭。
5. Metal 高性能渲染
对于游戏、复杂粒子系统或大量图形元素,Metal 是唯一能满足性能要求的方案。
5.1 基本渲染管线
swift
import MetalKit
class Renderer: NSObject, MTKViewDelegate {
func draw(in view: MTKView) {
guard let drawable = view.currentDrawable,
let commandBuffer = view.commandQueue?.makeCommandBuffer(),
let renderPassDescriptor = view.currentRenderPassDescriptor else { return }
let renderEncoder = commandBuffer.makeRenderCommandEncoder(descriptor: renderPassDescriptor)!
renderEncoder.endEncoding()
commandBuffer.present(drawable)
commandBuffer.commit()
}
}
5.2 何时选择 Metal
- 需要绘制大量动态几何体(如粒子、地图瓦片)。
- 需要自定义着色器实现特殊效果。
- 对帧率有严格要求(如 60fps 以上)。
6. 技术选型决策流程
#mermaid-svg-qP8qRkK7tLAverN8{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-qP8qRkK7tLAverN8 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-qP8qRkK7tLAverN8 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-qP8qRkK7tLAverN8 .error-icon{fill:#552222;}#mermaid-svg-qP8qRkK7tLAverN8 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-qP8qRkK7tLAverN8 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-qP8qRkK7tLAverN8 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-qP8qRkK7tLAverN8 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-qP8qRkK7tLAverN8 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-qP8qRkK7tLAverN8 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-qP8qRkK7tLAverN8 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-qP8qRkK7tLAverN8 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-qP8qRkK7tLAverN8 .marker.cross{stroke:#333333;}#mermaid-svg-qP8qRkK7tLAverN8 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-qP8qRkK7tLAverN8 p{margin:0;}#mermaid-svg-qP8qRkK7tLAverN8 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-qP8qRkK7tLAverN8 .cluster-label text{fill:#333;}#mermaid-svg-qP8qRkK7tLAverN8 .cluster-label span{color:#333;}#mermaid-svg-qP8qRkK7tLAverN8 .cluster-label span p{background-color:transparent;}#mermaid-svg-qP8qRkK7tLAverN8 .label text,#mermaid-svg-qP8qRkK7tLAverN8 span{fill:#333;color:#333;}#mermaid-svg-qP8qRkK7tLAverN8 .node rect,#mermaid-svg-qP8qRkK7tLAverN8 .node circle,#mermaid-svg-qP8qRkK7tLAverN8 .node ellipse,#mermaid-svg-qP8qRkK7tLAverN8 .node polygon,#mermaid-svg-qP8qRkK7tLAverN8 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-qP8qRkK7tLAverN8 .rough-node .label text,#mermaid-svg-qP8qRkK7tLAverN8 .node .label text,#mermaid-svg-qP8qRkK7tLAverN8 .image-shape .label,#mermaid-svg-qP8qRkK7tLAverN8 .icon-shape .label{text-anchor:middle;}#mermaid-svg-qP8qRkK7tLAverN8 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-qP8qRkK7tLAverN8 .rough-node .label,#mermaid-svg-qP8qRkK7tLAverN8 .node .label,#mermaid-svg-qP8qRkK7tLAverN8 .image-shape .label,#mermaid-svg-qP8qRkK7tLAverN8 .icon-shape .label{text-align:center;}#mermaid-svg-qP8qRkK7tLAverN8 .node.clickable{cursor:pointer;}#mermaid-svg-qP8qRkK7tLAverN8 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-qP8qRkK7tLAverN8 .arrowheadPath{fill:#333333;}#mermaid-svg-qP8qRkK7tLAverN8 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-qP8qRkK7tLAverN8 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-qP8qRkK7tLAverN8 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qP8qRkK7tLAverN8 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-qP8qRkK7tLAverN8 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qP8qRkK7tLAverN8 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-qP8qRkK7tLAverN8 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-qP8qRkK7tLAverN8 .cluster text{fill:#333;}#mermaid-svg-qP8qRkK7tLAverN8 .cluster span{color:#333;}#mermaid-svg-qP8qRkK7tLAverN8 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-qP8qRkK7tLAverN8 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-qP8qRkK7tLAverN8 rect.text{fill:none;stroke-width:0;}#mermaid-svg-qP8qRkK7tLAverN8 .icon-shape,#mermaid-svg-qP8qRkK7tLAverN8 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qP8qRkK7tLAverN8 .icon-shape p,#mermaid-svg-qP8qRkK7tLAverN8 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-qP8qRkK7tLAverN8 .icon-shape .label rect,#mermaid-svg-qP8qRkK7tLAverN8 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qP8qRkK7tLAverN8 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-qP8qRkK7tLAverN8 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-qP8qRkK7tLAverN8 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
否
是
绘制需求分析
是否需要高频刷新?
Core Graphics / draw(_:)
是否需要复杂特效?
Core Animation 图层
Metal 自定义渲染
7. 实战:绘制一个高效的自定义图表
下面以折线图为例,展示如何结合 Core Graphics 与 Core Animation 实现高效绘制。
swift
class LineChartView: UIView {
var points: [CGPoint] = [] {
didSet { setNeedsDisplay() }
}
override func draw(_ rect: CGRect) {
guard let context = UIGraphicsGetCurrentContext() else { return }
context.setStrokeColor(UIColor.systemBlue.cgColor)
context.setLineWidth(2)
context.beginPath()
for (index, point) in points.enumerated() {
if index == 0 {
context.move(to: point)
} else {
context.addLine(to: point)
}
}
context.strokePath()
}
}
对于数据点较多的情况,可将折线预渲染为 UIImage 并缓存,避免每次滚动都重新绘制。
8. 总结
iOS 绘图没有银弹,关键在于根据场景选择合适的技术层:
- 简单静态绘制 → Core Graphics。
- 高频动画与滚动 → Core Animation。
- 复杂特效与极致性能 → Metal。
理解各层的能力边界与性能特征,才能写出既流畅又易维护的绘图代码。