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