drawRect方法的理解

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

  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 使用率和帧率
相关推荐
子兮曰3 天前
jev-ultrafast 深度解析:7 秒订机票的浏览器 Agent 是如何炼成的
前端·后端·agent
子兮曰3 天前
Jev 爆发一周:7 秒 Agent 背后的 System One 生态与三场争议
前端·后端·ai编程
前端小万3 天前
写公众号赚了 3000 块后,我做了一款叫 "一键成稿" 的软件
前端·微信小程序
爱勇宝3 天前
ZCode 开源 24 小时:一份没有历史的账本,回答不了"有没有偷代码"
前端·后端·chatglm (智谱)
三十而立洋3 天前
Cookie 详解:从产生到安全,一次讲透
前端·javascript
卡布鲁3 天前
把一个 Vite + Vue3 应用塞进 qiankun (React + Umi3) 主站:十个坑的复盘
前端·javascript·react.js
汉堡大王95273 天前
Jev:不是聊天机器人, 而是一个智能 if 语句
前端·人工智能·后端
梦想很大很大3 天前
从运行事实到回归证据:Workrun 的 Telemetry 与 Evaluation 实践
前端·人工智能·后端
计算机魔术师3 天前
Meta Muse agent 接入 Shopify 的 Shop Pay 实现代理式购物
前端
沙蒿同学3 天前
我用 Go 搭了一条 AI Agent 流水线:从 1 张商品图到一整套淘宝详情页
前端·javascript·后端