drawRect方法的理解

drawRect: 是 UIView 中用于自定义绘制内容的核心方法,对iOS开发者来说,想要高效绘图,我们需要深入理解这个方法。

一、基础调用时机

1. 首次显示视图时

  • 当视图被添加到视图层级时
  • 当视图的 hidden 属性从 YES 变为 NO 时
  • 当视图从父视图的 nil 变为非 nil

2. 视图尺寸变化时

  • 当视图的 framebounds 属性改变时
  • 设备旋转导致视图尺寸变化时
  • 父视图布局改变导致子视图尺寸变化时

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. 系统绘制周期

  1. ​RunLoop 的 BeforeWaiting 阶段​:系统检查所有标记为需要重绘的视图
  2. ​合并绘制请求​ :将多个 setNeedsDisplay 调用合并为一次绘制
  3. ​调用顺序​:按照视图层级从父视图到子视图依次调用

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 使用率和帧率
相关推荐
Nan_Shu_61419 分钟前
学习: Threejs (2)
前端·javascript·学习
G_G#27 分钟前
纯前端js插件实现同一浏览器控制只允许打开一个标签,处理session变更问题
前端·javascript·浏览器标签页通信·只允许一个标签页
@大迁世界43 分钟前
TypeScript 的本质并非类型,而是信任
开发语言·前端·javascript·typescript·ecmascript
GIS之路1 小时前
GDAL 实现矢量裁剪
前端·python·信息可视化
是一个Bug1 小时前
后端开发者视角的前端开发面试题清单(50道)
前端
Amumu121381 小时前
React面向组件编程
开发语言·前端·javascript
持续升级打怪中1 小时前
Vue3 中虚拟滚动与分页加载的实现原理与实践
前端·性能优化
GIS之路1 小时前
GDAL 实现矢量合并
前端
hxjhnct1 小时前
React useContext的缺陷
前端·react.js·前端框架
前端 贾公子2 小时前
从入门到实践:前端 Monorepo 工程化实战(4)
前端