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 防止滚动时重新加载的主要内容,如果未能解决你的问题,请参考以下文章
每次我重新加载 tableview 部分时 UITableView 都不会向上滚动