大文本下ScrollView的targetContentOffset不准是iOS Bug吗?
核心结论
该现象不属于iOS接口Bug,是UITextView针对大文本场景的性能优化机制带来的预期内偏差。
问题复现代码
场景为UITextView加载10万行文本,对比scrollViewWillEndDragging回调拿到的预估停止位置和滚动停稳后的实际contentOffset:
- (void)viewDidLoad { [super viewDidLoad]; self.textView.delegate = self; NSMutableString *mustr = [[NSMutableString alloc]init]; for (int i = 0; i < 100000; i++) { [mustr appendFormat:@"%i\n",i]; } self.textView.text = mustr; } - (void)scrollViewWillEndDragging:(UIScrollView *)scrollView withVelocity:(CGPoint)velocity targetContentOffset:(inout CGPoint *)targetContentOffset{ // 原代码存在两处笔误:log文案重复、引用了未定义的willEndPoint变量,正确取值为targetContentOffset->y NSLog(@"Get expected stop position:%f", targetContentOffset->y); } - (IBAction)TESTBTN:(id)sender { NSLog(@"Final roll stop position:%f",self.textView.contentOffset.y); }
测试中可观察到明确规律:滑动距离越长,预估值和实际停止位置的偏差越大。
偏差产生的具体原因
- 从接口设计上,
scrollViewWillEndDragging:withVelocity:targetContentOffset:返回的targetContentOffset本身就是预估值:它是拖拽松手瞬间,系统基于当前时刻已确定的contentSize、松手速度、默认滚动减速率计算出的理论停靠位置,从设计上就不是最终精确结果。 - UITextView底层基于TextKit实现,面对10万行级别的超大文本,为了避免首屏加载卡顿、降低内存占用,不会在文本赋值时一次性完成全量内容的行高、位置排版计算,仅会对当前可视区域、滚动方向临近区域的文本做精确布局,远离可视区的内容暂时使用估算高度。
- 滚动减速过程中,系统会随着滚动位置的推进,实时对即将进入可视区的文本补做排版计算,同步更新contentSize数值。滑动距离越长,减速过程中需要补算的排版内容越多,contentSize的动态调整累积量越大,和松手瞬间计算预估值时用到的contentSize差值就越高,最终表现为预估值和实际停止位置的偏差随滑动距离增大而升高。
适配方案
- 若业务需要获取滚动停止后的精确偏移量,不要依赖
scrollViewWillEndDragging中的预估结果,直接实现scrollViewDidEndDecelerating:代理方法,在回调中读取实时的contentOffset即可,该时机拿到的是滚动完全停稳后的精确值。 - 若需要从根源降低这类偏差,同时提升大文本场景的滚动流畅度,不要直接将10万行级别的全量文本直接赋值给UITextView,可采用虚拟滚动、分页加载的方案,仅渲染当前可视区域附近的文本内容,避免边滚动边补算排版带来的估算误差。
内容的提问来源于stack exchange,提问作者liangdapang
相关产品推荐
相关产品推荐

