drawRect: 是 UIView 中用于自定义绘制内容的核心方法,对iOS开发者来说,想要高效绘图,我们需要深入理解这个方法。
一、基础调用时机
1. 首次显示视图时
- 当视图被添加到视图层级时
- 当视图的
hidden 属性从 YES 变为 NO 时
- 当视图从父视图的
nil 变为非 nil 时
2. 视图尺寸变化时
- 当视图的
frame 或 bounds 属性改变时
- 设备旋转导致视图尺寸变化时
- 父视图布局改变导致子视图尺寸变化时
3. 显式请求重绘时
- 调用
setNeedsDisplay 方法
- 调用
setNeedsDisplayInRect: 方法(部分重绘)
二、详细调用场景分析
1. 自动调用场景
class CustomView: UIView {
override func draw(_ rect: CGRect) {
super.draw(rect)
print("drawRect called with rect: (rect)")
}
}
let view = CustomView(frame: CGRect(x: 0, y: 0, width: 100, height: 100))
window.addSubview(view)
view.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
view.isHidden = true
view.isHidden = false
2. 手动触发场景
view.setNeedsDisplay()
view.setNeedsDisplay(CGRect(x: 10, y: 10, width: 50, height: 50))
三、调用机制原理
1. 系统绘制周期
- RunLoop 的 BeforeWaiting 阶段:系统检查所有标记为需要重绘的视图
- 合并绘制请求:将多个
setNeedsDisplay 调用合并为一次绘制
- 调用顺序:按照视图层级从父视图到子视图依次调用
2. 性能优化机制
- 延迟合并:系统不会立即响应每次属性变化,而是在下一个绘制周期统一处理
- 脏矩形技术:只重绘发生变化的部分区域(通过
rect 参数传递)
四、重要注意事项
1. 不要直接调用 drawRect:
view.draw(CGRect.zero)
view.setNeedsDisplay()
2. 绘制性能影响
- 频繁调用
drawRect: 会严重影响性能
- 复杂绘制应考虑使用 CAShapeLayer 或 Core Graphics 离屏渲染
3. 背景色与绘制
- 设置
backgroundColor 不会触发 drawRect:
- 如果自定义了
drawRect:,背景色需要在方法内手动绘制
五、高级调用场景
1. 内容模式影响
view.contentMode = .redraw // 尺寸变化时自动调用 drawRect:
view.contentMode = .scaleToFill // 默认模式,不自动触发重绘
2. 动画中的调用
UIView.animate(withDuration: 1.0) {
view.frame = CGRect(x: 0, y: 0, width: 300, height: 300)
}
3. 滚动视图中的调用
scrollView.didScroll {
visibleCells.forEach { $0.setNeedsDisplay() }
}
六、实践建议
1. 减少不必要的重绘
func updatePartialContent() {
let dirtyRect = CGRect(x: 10, y: 10, width: 50, height: 50)
setNeedsDisplay(dirtyRect)
}
2. 复杂绘制优化
let displayLink = CADisplayLink(target: self, selector: #selector(updateDrawing))
displayLink.preferredFramesPerSecond = 30
displayLink.add(to: .current, forMode: .common)
3. 离屏渲染技术
DispatchQueue.global(qos: .userInitiated).async {
UIGraphicsBeginImageContextWithOptions(size, false, 0)
defer { UIGraphicsEndImageContext() }
let image = UIGraphicsGetImageFromCurrentImageContext()
DispatchQueue.main.async {
imageView.image = image
}
}
七、 性能分析
- 使用 Instruments 的 Core Animation 模板
- 检查
drawRect: 的执行时间和频率
- 监控 CPU 使用率和帧率