UITableView可防止滚动时重新加载

时间:2014-08-04 00:50:53

标签: ios objective-c uitableview reloaddata

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

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

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

它应该是什么样子,它实际上是什么:

what it should look like what it actually looks like

^^^^^^^^^^^^ 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:调用吗?或者你能想象一个防止这种行为的好方法吗?

3 个答案:

答案 0 :(得分:7)

基于Maltesavner的想法(请同时回答他的回答)我可以实施一个解决方案。他的答案并没有成功,但这是正确的方向。

我必须实施-scrollViewDidEndScrollingAnimation:。我为滚动时重新加载的索引路径创建了一个名为_autoScrolling的bool属性和一个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中写了更详细的解释。

答案 1 :(得分:6)

抱歉,我没有足够的声誉来添加评论,因此在单独的答案中回答了您的上一个问题。

延迟为0.0秒的

-performSelector:withObject:afterDelay:不会立即执行给定的选择器,而是在当前的Runloop Cycle结束后以及在给定的延迟之后执行它。

在当前的Runloop循环中添加-performSelector:withObject:并执行的位置。这与直接调用该方法相同。

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

来源:Apple Dev Docsthis Thread Answer

答案 2 :(得分:3)

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