UITableView 防止滚动时重新加载

Posted

技术标签:

【中文标题】UITableView 防止滚动时重新加载【英文标题】:UITableView prevent reloading while scrolling 【发布时间】:2014-08-04 00:50:53 【问题描述】:

我已经实现了一个 UITableView 加载更多功能。 tableView 从有时很慢的服务器加载大图像。我正在为每个图像启动一个 URLConnection 并重新加载与 URLConnection 对应的 indexPath(与连接对象一起保存)。连接本身在 tableView 上调用-reloadData

现在,当单击加载更多按钮时,我滚动到位置底部的新数据集的第一行。这很好用,也是我的异步加载系统。

我遇到了以下问题:当连接“太快”时,tableView 正在重新加载给定 indexPath 的数据,而 tableView 仍在滚动到新数据集的第一个单元格,tableView 向后滚动一半该单元格的高度。

这就是它的外观和实际作用:

^^^^^^^^^^^^ should ^^^^^^^^^^^^ ^^^^^^^^^^^^^ does ^^^^^^^^^^^^^

这里有一些代码:

[[self tableView] beginUpdates];
for (NSMutableDictionary *post in object) 
    [_dataSource addObject:post];
    [[self tableView] insertRowsAtIndexPaths:@[[NSIndexPath indexPathForRow:[_dataSource indexOfObject:post] inSection:0]] withRowAnimation:UITableViewRowAnimationBottom];

[[self tableView] endUpdates];

[[self tableView] scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:[_dataSource indexOfObject:[object firstObject]] inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES];

-tableView:cellForRowAtIndexPath: 如果数据源数组中的对象是字符串,则启动 JWURLConnection,并在完成块中将其替换为 UIImage 的实例。然后它重新加载给定的单元格:

id image = [post objectForKey:@"thumbnail_image"];

if ([image isKindOfClass:[NSString class]]) 
    JWURLConnection *connection = [JWURLConnection connectionWithGETRequestToURL:[NSURL URLWithString:image] delegate:nil startImmediately:NO];
    [connection setFinished:^(NSData *data, NSStringEncoding encoding) 
        [post setObject:[UIImage imageWithData:data] forKey:@"thumbnail_image"];
        [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
    ];

    [cell startLoading];
    [connection start];

else if ([image isKindOfClass:[UIImage class]]) 
    [cell stopLoading];
    [cell setImage:image];

else 
    [cell setImage:nil];

我可以在 tableView 滚动完成之前阻止 tableView 执行-reloadRowsAtIndexPaths:withRowAnimation: 调用吗?或者您能想出一种防止这种行为的好方法吗?

【问题讨论】:

【参考方案1】:

基于Malte 和savner 的想法(也请赞成他的回答)我可以实施一个解决方案。他的回答没有起到作用,但这是正确的方向。

我必须实现-scrollViewDidEndScrollingAnimation:。我为滚动时重新加载的索引路径创建了一个名为_autoScrolling 的布尔属性和一个NSMutableArray 属性。在 URLConnections 完成块中,我这样做了:

if (_autoScrolling) 
    if (!_indexPathsToReload) 
        _indexPathsToReload = [NSMutableArray array];
    
    [_indexPathsToReload addObject:indexPath];

else 
    [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];

然后是这个:

- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView 
    [self performSelector:@selector(performRelodingAfterAutoScroll) withObject:nil afterDelay:0.0];


- (void)performRelodingAfterAutoScroll 
    _autoScrolling = NO;

    if (_indexPathsToReload) 
        [[self tableView] reloadRowsAtIndexPaths:_indexPathsToReload withRowAnimation:UITableViewRowAnimationFade];
    

    _indexPathsToReload = nil;

我花了很长时间才找到-performSelector:withObject:afterDelay: 的诀窍,但我仍然不知道我为什么需要它。

我认为该方法可能被调用得太早了。所以我实施了一秒钟的延迟,并尝试了我能把它拿下来多远。它仍然适用于0.0,但如果我直接调用该方法或使用-performSelector:withObject:,则不行。

我真的希望有人能解释一下。

编辑

几年后重新审视这一点后,我可以解释这里发生了什么:

调用-[NSObject (NSDelayedPerforming) performSelector:withObject:afterDelay:]保证调用在下一次runloop迭代中执行。

因此,更好或更漂亮的解决方案是:

[[NSOperationQueue currentQueue] addOperationWithBlock:^
    [self performRelodingAfterAutoScroll];
];

我在this answer写了更详细的解释。

【讨论】:

【参考方案2】:

对不起,我没有足够的声誉来添加评论,因此您最后一个问题的答案在单独的答案中。

-performSelector:withObject:afterDelay: 延迟 0.0 秒不会立即执行给定的选择器,而是在当前的 Runloop Cycle 完成和给定的延迟之后执行。

-performSelector:withObject: 被添加到当前 Runloop Cycle 并在其中执行。和直接调用方法是一样的。

因此,使用-performSelector:withObject:afterDelay:,UI 将在当前的 Runloop Cycle 中更新,即在这种情况下,滚动动画可以在您的选择器执行之前完成(并再次重新加载 UI)。

来源:Apple Dev Docs 和 this Thread Answer

【讨论】:

太棒了!感谢您的澄清!我愿意接受,但不幸的是这不是我的 PO =) 没关系。至少我越来越接近能够添加 cmets。 :) 很高兴我能帮上忙。【参考方案3】:

您可以使用 UIScrollViewDelegate 协议(您可以使用 UITableViewDelegate 免费获得)并利用 -scrollViewDidScroll 或 -scrollViewWillBeginDragging:检测滚动已开始或停止的方法。使用这些回调来控制何时加载/停止加载单元格数据。

【讨论】:

这是一个很好的方向,但不幸的是不是我想要的答案,所以我不能接受你的答案。加油吧,谢谢 :)

以上是关于UITableView 防止滚动时重新加载的主要内容,如果未能解决你的问题,请参考以下文章

UITableView如何在重新加载后继续滚动

在 UITableView 中缓存图像

UITableview 在重新加载时滚动到顶部太高?

每次我重新加载 tableview 部分时 UITableView 都不会向上滚动

重新加载单元格高度变化的单元格时,UITableView 滚动到顶部

重新加载 UITableView 数据并向下滚动到最后选择的单元格