在 iOS 开发中,**UIView**
的 **drawRect:**
方法(或其底层 **CALayer**
的绘制)的触发时机是由系统控制的,开发者不能直接调用这些方法。以下是触发视图绘制的完整机制:
一、核心触发时机
1. 视图首次显示
当视图被添加到视图层级时:
[self.view addSubview:customView]; // 触发首次绘制
2. 显式标记需要重绘
调用以下方法强制重绘:
// 标记整个视图需要重绘
[customView setNeedsDisplay];
// 标记部分区域需要重绘(优化性能)
[customView setNeedsDisplayInRect:CGRectMake(0,0,50,50)];
3. 视图几何属性变化
customView.frame = newFrame; // 位置/尺寸变化
customView.bounds = newBounds; // 坐标系变化
customView.transform = CGAffineTransformMakeRotation(M_PI/4); // 形变
4. 内容模式改变
当 **contentMode**
设为重绘模式:
customView.contentMode = UIViewContentModeRedraw; // 尺寸变化时触发重绘
5. 关联数据变化
// 数据变化时触发重绘
- (void)setDataModel:(DataModel *)model {
_model = model;
[self setNeedsDisplay]; // 手动触发
}
二、系统级自动触发场景
1. RunLoop 周期处理
2. 滚动视图刷新
// UIScrollView 滚动时
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
[visibleCells makeObjectsPerformSelector:@selector(setNeedsDisplay)];
}
3. 系统事件触发
// 设备旋转
- (void)viewWillTransitionToSize:(CGSize)size {
[self.view setNeedsDisplay];
}
// 深色模式切换
- (void)traitCollectionDidChange:(UITraitCollection *)previous {
[self updateAppearance]; // 触发重绘
}
三、**CALayer**
专用触发机制
1. 图层属性变化
layer.contents = newImage; // 内容替换
layer.cornerRadius = 10.0; // 外观变化
layer.borderWidth = 2.0; // 边框变化
2. 强制重绘方法
[layer setNeedsDisplay]; // 异步标记
[layer displayIfNeeded]; // 同步立即绘制
[layer setNeedsDisplayInRect:invalidRect]; // 局部重绘
3. 动画过渡重绘
[CATransaction begin];
[CATransaction setAnimationDuration:0.5];
layer.backgroundColor = [UIColor blueColor].CGColor; // 触发隐式动画
[CATransaction commit];
四、优化绘制的关键实践
1. 避免过度绘制
// 检查是否需要重绘
- (void)setData:(NSArray *)data {
if ([_data isEqualToArray:data]) return;
_data = data;
[self setNeedsDisplay]; // 仅数据变化时触发
}
2. 智能区域重绘
// 只重绘变化区域
- (void)updateItemAtIndex:(NSInteger)index {
CGRect dirtyRect = [self rectForItemAtIndex:index];
[self setNeedsDisplayInRect:dirtyRect];
}
3. 绘制状态管理
// 在视图中管理绘制状态
- (void)drawRect:(CGRect)rect {
if (self.isDrawingDisabled) return; // 跳过条件
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[self drawStaticBackground]; // 只绘制一次
});
[self drawDynamicContentInRect:rect]; // 局部绘制
}
4. 离屏绘制优化
// 后台预渲染
dispatch_async(renderQueue, ^{
UIGraphicsBeginImageContextWithOptions(size, NO, 0);
[self renderContent];
UIImage *preRendered = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
dispatch_async(dispatch_get_main_queue(), ^{
self.layer.contents = (id)preRendered.CGImage;
});
});
五、调试与性能检测
1. 绘制调试工具
// 颜色标记重绘区域
- (void)drawRect:(CGRect)rect {
[[UIColor colorWithRed:1 green:0 blue:0 alpha:0.1] setFill];
UIRectFill(rect); // 显示重绘区域
// 实际绘制内容...
}
2. Instruments 检测
- 使用 Core Animation 工具:
- 开启 Color Misaligned Images
- 开启 Color Offscreen-Rendered Yellow
- 使用 Time Profiler 检测
**drawRect:**
CPU 耗时
3. 绘制耗时监控
- (void)drawRect:(CGRect)rect {
CFTimeInterval start = CACurrentMediaTime();
// 绘制操作...
CFTimeInterval duration = CACurrentMediaTime() - start;
if (duration > 0.016) { // 超过16ms警告
NSLog(@"⚠️ 绘制耗时过长: %.2fms", duration * 1000);
}
}
六、特殊场景处理
1. 滚动视图优化
// UITableViewCell 绘制控制
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
Cell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
cell.drawingEnabled = tableView.isDecelerating || tableView.isDragging; // 滚动时禁用复杂绘制
return cell;
}
2. 内存警告处理
- (void)didReceiveMemoryWarning {
[self clearRenderedCache]; // 释放绘制缓存
}
3. 后台绘制管理
// 进入后台时暂停绘制
- (void)applicationDidEnterBackground {
[self.layer.contents = nil]; // 释放内容
[self cancelAsyncDraw]; // 取消异步任务
}
总结:绘制触发的黄金法则
- 绝不直接调用** **
**drawRect:**
始终通过**setNeedsDisplay**
系列方法触发 - 最小化重绘区域
使用**setNeedsDisplayInRect:**
精确控制 - 分离静态与动态内容
静态内容预渲染,动态内容局部更新 - 监控绘制性能
确保单帧绘制 ≤ 16ms(60FPS) - 适配系统生命周期
正确处理后台/内存警告等场景
📌 关键提示:在 **scrollViewDidScroll**
等高频回调中,避免无条件调用 **setNeedsDisplay**
,应使用节流机制:
// 滚动时每帧最多触发一次
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
static CFTimeInterval lastTime = 0;
CFTimeInterval now = CACurrentMediaTime();
if (now - lastTime > 0.016) { // 60FPS间隔
[self setNeedsDisplay];
lastTime = now;
}
}