【问题标题】:UITableView prevent reloading while scrollingUITableView 防止滚动时重新加载
【发布时间】: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: 调用吗?或者您能想出一种防止这种行为的好方法吗?

【问题讨论】:

    标签: ios objective-c uitableview reloaddata


    【解决方案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:检测滚动已开始或停止的方法。使用这些回调来控制何时加载/停止加载单元格数据。

      【讨论】:

      • 这是一个很好的方向,但不幸的是不是我想要的答案,所以我不能接受你的答案。加油吧,谢谢 :)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-06-16
      • 1970-01-01
      • 1970-01-01
      • 2011-10-22
      • 2014-08-06
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多