文档背景:最近遇到个需求,一个静态数据表格,需要默认滚动中间位置, 起初感觉没什么,后面发现这里还有点意思,所以记录一下
- (void)viewDidLoad {
[super viewDidLoad];
/// 初始化tableView
UITableView *tableView = [self ld_basesetupTableView:GKLDBaseVIewControllerStyleGroup];
//[tableView setContentOffset:CGPointMake(0, 100) animated:NO];
[tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:0] atScrollPosition:UITableViewScrollPositionNone animated:YES];
}
上述是最简单的代码结构,执行你会发现表格其实并未滚动
开始排查
断点打在执行滚动的代码位置,你会发现tableView的contentSize等于(0,0), 这说明tableView其实并未渲染完,也就是数据源函数未执行完。
那么怎么确定数据源函数有没有执行完呢?
因为 UITableView
是基于数据源的,所以数据源函数的执行时间是不确定的,取决于数据源中的数据量和计算布局所需要的时间等因素。如果数据源中的数据量非常大,或者单元格的计算布局非常复杂,那么数据源函数的执行时间可能会比较长。
方法1 通过改变执行滚动代码的位置到vc不同的生命周期函数
- (void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
//[_tableView setContentOffset:CGPointMake(0, 100) animated:NO];
//[_tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:0] atScrollPosition:UITableViewScrollPositionNone animated:NO];
[_tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:0] atScrollPosition:UITableViewScrollPositionNone animated:YES];
}
- (void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
//[_tableView setContentOffset:CGPointMake(0, 100) animated:NO];
//[_tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:0] atScrollPosition:UITableViewScrollPositionNone animated:NO];
[_tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:0] atScrollPosition:UITableViewScrollPositionNone animated:YES];
}
结果发现还是不生效
因为:
即使在 viewDidLayoutSubviews
,viewDidAppear
中,UITableView
的布局可能仍未准备就绪。
好,为了确保数据源执行完毕,那么滚动代码就必须要在下一次运行循环中执行
方式二
[tableView reloadData];
[tableView layoutIfNeeded];
[_tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:0] atScrollPosition:UITableViewScrollPositionNone animated:YES];
在tableView加载数据源后,运行方法是简单,强制UITableView立即执行布局, 与数据源大小,cell的样式复杂度有关,如果是数据量大,且复杂的 ,失败率高
方式三
[tableView reloadData];
dispatch_async(dispatch_get_main_queue(), ^{
[tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:0] atScrollPosition:UITableViewScrollPositionNone animated:YES];
});
在tableView加载数据源后,执行异步往主线程添加一个任务
dispatch_async(dispatch_get_main_queue()) 不能保证方法有效。 因为它的非确定性行为,有时系统在完成之前完成了 layoutSubviews 和单元格渲染,有时在完成之后。 需要看当前运行循环的任务数
方式三
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:0] atScrollPosition:UITableViewScrollPositionNone animated:YES];
});
使用 dispatch_after 添加一个延迟任务,但延迟时间可以设置为0,根据复杂度决定
并不保证一定会在下一次运行循环中执行,但一般情况下,它的执行确实会被安排在下一次运行循环中。
具体来说,当我们调用 dispatch_after
函数时,它会将任务添加到指定的队列中,并在指定的时间后将任务从队列中取出执行。在实际执行过程中,如果当前运行循环中有其他任务在执行,那么 dispatch_after
函数可能需要等待当前任务执行完毕后才能开始执行延迟的任务。因此,如果当前运行循环中有大量任务在等待执行,那么 dispatch_after
函数可能需要等待比预期的更长的时间才能开始执行任务。
这也就是好多代码为什么放进延迟函数里执行就能生效, 这个主要还是因为运行循环任务执行的原因
同理:UICollectionView 也是一样的
不过项目中,大部分表格的位置的滚动都是在数据处理之后,或网络请求回调中,这个时候再去调用基本是正常的,无需考虑上述情况,上述情况主要考虑静态数据表格初始化时就要定位到其他位置的特殊场景。
文章质量不高,只是感觉有意思,所以记录一下,感谢阅读