在 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;}
}