文檔背景:最近遇到個需求,一個靜態(tài)數(shù)據(jù)表格,需要默認滾動中間位置, 起初感覺沒什么,后面發(fā)現(xiàn)這里還有點意思,所以記錄一下
- (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];
}
上述是最簡單的代碼結(jié)構(gòu),執(zhí)行你會發(fā)現(xiàn)表格其實并未滾動
開始排查
斷點打在執(zhí)行滾動的代碼位置,你會發(fā)現(xiàn)tableView的contentSize等于(0,0), 這說明tableView其實并未渲染完,也就是數(shù)據(jù)源函數(shù)未執(zhí)行完。
那么怎么確定數(shù)據(jù)源函數(shù)有沒有執(zhí)行完呢?
因為 UITableView 是基于數(shù)據(jù)源的,所以數(shù)據(jù)源函數(shù)的執(zhí)行時間是不確定的,取決于數(shù)據(jù)源中的數(shù)據(jù)量和計算布局所需要的時間等因素。如果數(shù)據(jù)源中的數(shù)據(jù)量非常大,或者單元格的計算布局非常復(fù)雜,那么數(shù)據(jù)源函數(shù)的執(zhí)行時間可能會比較長。
方法1 通過改變執(zhí)行滾動代碼的位置到vc不同的生命周期函數(shù)
- (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];
}
結(jié)果發(fā)現(xiàn)還是不生效
因為:
即使在 viewDidLayoutSubviews,viewDidAppear 中,UITableView 的布局可能仍未準備就緒。
好,為了確保數(shù)據(jù)源執(zhí)行完畢,那么滾動代碼就必須要在下一次運行循環(huán)中執(zhí)行
方式二
[tableView reloadData];
[tableView layoutIfNeeded];
[_tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:0] atScrollPosition:UITableViewScrollPositionNone animated:YES];
在tableView加載數(shù)據(jù)源后,運行方法是簡單,強制UITableView立即執(zhí)行布局, 與數(shù)據(jù)源大小,cell的樣式復(fù)雜度有關(guān),如果是數(shù)據(jù)量大,且復(fù)雜的 ,失敗率高
方式三
[tableView reloadData];
dispatch_async(dispatch_get_main_queue(), ^{
[tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:1 inSection:0] atScrollPosition:UITableViewScrollPositionNone animated:YES];
});
在tableView加載數(shù)據(jù)源后,執(zhí)行異步往主線程添加一個任務(wù)
dispatch_async(dispatch_get_main_queue()) 不能保證方法有效。 因為它的非確定性行為,有時系統(tǒng)在完成之前完成了 layoutSubviews 和單元格渲染,有時在完成之后。 需要看當(dāng)前運行循環(huán)的任務(wù)數(shù)
方式三
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 添加一個延遲任務(wù),但延遲時間可以設(shè)置為0,根據(jù)復(fù)雜度決定
并不保證一定會在下一次運行循環(huán)中執(zhí)行,但一般情況下,它的執(zhí)行確實會被安排在下一次運行循環(huán)中。
具體來說,當(dāng)我們調(diào)用 dispatch_after 函數(shù)時,它會將任務(wù)添加到指定的隊列中,并在指定的時間后將任務(wù)從隊列中取出執(zhí)行。在實際執(zhí)行過程中,如果當(dāng)前運行循環(huán)中有其他任務(wù)在執(zhí)行,那么 dispatch_after 函數(shù)可能需要等待當(dāng)前任務(wù)執(zhí)行完畢后才能開始執(zhí)行延遲的任務(wù)。因此,如果當(dāng)前運行循環(huán)中有大量任務(wù)在等待執(zhí)行,那么 dispatch_after 函數(shù)可能需要等待比預(yù)期的更長的時間才能開始執(zhí)行任務(wù)。
這也就是好多代碼為什么放進延遲函數(shù)里執(zhí)行就能生效, 這個主要還是因為運行循環(huán)任務(wù)執(zhí)行的原因
同理:UICollectionView 也是一樣的
不過項目中,大部分表格的位置的滾動都是在數(shù)據(jù)處理之后,或網(wǎng)絡(luò)請求回調(diào)中,這個時候再去調(diào)用基本是正常的,無需考慮上述情況,上述情況主要考慮靜態(tài)數(shù)據(jù)表格初始化時就要定位到其他位置的特殊場景。
文章質(zhì)量不高,只是感覺有意思,所以記錄一下,感謝閱讀