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 使用率和帧率
相关推荐
灵感__idea6 小时前
Hello 算法:贪心的世界
前端·javascript·算法
GreenTea8 小时前
一文搞懂Harness Engineering与Meta-Harness
前端·人工智能·后端
killerbasd9 小时前
牧苏苏传 我不装了 4/7
前端·javascript·vue.js
吴声子夜歌9 小时前
ES6——二进制数组详解
前端·ecmascript·es6
码事漫谈10 小时前
手把手带你部署本地模型,让你Token自由(小白专属)
前端·后端
ZC跨境爬虫10 小时前
【爬虫实战对比】Requests vs Scrapy 笔趣阁小说爬虫,从单线程到高效并发的全方位升级
前端·爬虫·scrapy·html
爱上好庆祝10 小时前
svg图片
前端·css·学习·html·css3
王夏奇10 小时前
python中的__all__ 具体用法
java·前端·python
大家的林语冰11 小时前
《前端周刊》尤大开源 Vite+ 全家桶,前端工业革命启动;尤大爆料 Void 云服务新产品,Vite 进军全栈开发;ECMA 源码映射规范......
前端·javascript·vue.js
jiayong2311 小时前
第 8 课:开始引入组合式函数
前端·javascript·学习