Cocoa touch 如何有效地使用动画更新UITableView?

Cocoa touch 如何有效地使用动画更新UITableView?,cocoa-touch,animation,uitableview,feed,Cocoa Touch,Animation,Uitableview,Feed,我的iPad应用程序具有一个通过提要填充的UITableView。与大多数RSS阅读器一样,它会按时间倒序显示博客文章的链接列表,并附有标题和每篇文章的摘要。feed经常更新,而且相当大,大约有500篇文章。我正在使用它在NSOperation子类中高效地下载和解析提要,构建条目对象并在运行时更新数据库。但是我需要用更改来更新UITableView 到目前为止,该应用程序一直在更新每次解析后的UITableView。解析器在主线程上执行选择器来完成这项工作。但是如果需要更新很多单元格,这会导致几

我的iPad应用程序具有一个通过提要填充的UITableView。与大多数RSS阅读器一样,它会按时间倒序显示博客文章的链接列表,并附有标题和每篇文章的摘要。feed经常更新,而且相当大,大约有500篇文章。我正在使用它在NSOperation子类中高效地下载和解析提要,构建条目对象并在运行时更新数据库。但是我需要用更改来更新UITableView

到目前为止,该应用程序一直在更新每次解析后的UITableView。解析器在主线程上执行选择器来完成这项工作。但是如果需要更新很多单元格,这会导致几秒钟的严重延迟。我可以通过在后台线程上运行表更新来缓解这一问题,但这似乎是可行的。所以现在我想弄清楚如何在主线程上更有效地更新表

当所有帖子都被解析后,我可以直接调用
reloadData
,但这对用户来说不是很友好:没有任何动画来表明有任何变化,只有一个闪光灯和新的数据在那里。我更愿意用动画来显示新帖子的添加和旧帖子的删除。未从提要中删除的现有帖子应通过顶部出现的新帖子向下推

我知道这是可能的,举个例子,他做得很好。每次从UITableView中添加或删除每篇文章,没有显示表格背景的间隙。所有这些都不会使UI有丝毫的不响应。这是怎么做到的

我最近的一次尝试是仅在解析了所有POST之后才更新表(解析器非常快,因此没有太多延迟)。然后,它加载NSDictionary中的现有POST,将其ID映射到用作表数据源的数组中的索引。然后,它迭代新解析的POST数组中的每个对象,将每个对象的NSIndexPath添加到随后传递给
-insertRowsAtIndexPaths:withRowAnimation:
-deleteRowsAtIndexPaths:withRowAnimation:
,以及
-reloadRowsAtIndexPaths:withRowAnimation:
的数组中,以便插入、删除、移动、,或更新单元格。对于500篇文章,更新大约需要4秒钟,UI完全没有响应。这段时间几乎完全用于UITableView动画更新;迭代两个POST数组所需的时间非常少

然后,我对其进行了修改,以便在不使用动画的情况下对其进行更新,并且我有单独的数组,仅针对与当前可见行相对应的行位置插入/删除/重新加载动画。这样更好,但在删除帖子和添加新帖子时会出现空白

很抱歉,这太冗长了,但结果是:


我如何更新一个UITableView,新的单元格被推上,其他单元格被推下,还有其他单元格从一个位置移动到另一个位置,UITableView中最多有500个单元格(6-8个单元格一次可见),并且每个动画都按顺序发生,而UI保持完全响应?

您是否尝试过
[tableView BeginUpdate]
[tableView endUpdate]

这个问题实际上有三个答案。也就是说,这个问题分为三个部分:

  • 如何保持UI的响应性
  • 如何保持快速更新
  • 如何使表格更新动画平滑
  • 用户界面响应 为了解决第一个问题,我现在确保在主事件循环的每次迭代中不能传递超过一条表更新消息。这可以防止主线程在后台线程以超出其处理速度的速度输入内容时锁定

    这要感谢作者Milo Bird发给我的示例代码,然后我将其集成到了的中。此接口使将要在主事件循环的下一个可用迭代中调用的方法排队变得非常容易:

    [[(id)delegate queueOnMainThread]
        parserParsedEntries:parsedEntries
                   inPortal:parsedPortal];
    
    我很喜欢使用这种方法是多么容易。解析器现在使用它来调用所有委托方法,其中大部分更新UI。我已经发布了这个代码

    演出 至于性能,我最初是一次更新一行UITableView。这是有效的,但有点低效。我回头研究了这个示例,注意到解析器在发送到主线程更新表之前一直在等待,直到收集了10项。这是一次更新所有500行以保持性能而不使UI锁定的关键。我在一次调用中更新了1行、10行和所有500行,而更新10行似乎是性能和UI锁定之间的最佳折衷。五个可能也能很好地发挥作用

    动画 最后,还有动画。在观看会话时,我意识到我使用的
    deleteRowsAtIndexPaths:withRowAnimation:
    updateRowsAtIndexPaths:withRowAnimation:
    方法是错误的。我一直在跟踪应该在表中添加和删除哪些内容,并根据需要调整索引,但事实证明这是不必要的。在表更新块中,只需要引用更新之前的行索引,而不管插入或删除多少行以更改其位置。显然,更新块为您完成了所有簿记工作。(有关关键示例,请转到视频中的8:45标记)

    因此,根据解析器传递给表的条目数(目前为每次10条)更新表的委托方法现在显式地跟踪更新块之前要更新或删除的行的位置,如下所示:

    NSMutableDictionary *oldIndexFor = [NSMutableDictionary dictionaryWithCapacity:posts.count];
    int i = 0;
    for (PostModel *e in  posts) {
        [oldIndexFor setObject:[NSNumber numberWithInt:i++] forKey:e.ident];
    }
    
    NSMutableArray *insertPaths = [NSMutableArray array];
    NSMutableArray *deletePaths = [NSMutableArray array];
    NSMutableArray *reloadPaths = [NSMutableArray array];
    BOOL modified = NO;
    
    for (PostModel *entry in entries) {
        NSNumber *num = [oldIndexFor objectForKey:entry.ident];
        NSIndexPath *path = [NSIndexPath indexPathForRow:currentPostIndex inSection:0];
        if (num == nil) {
            modified = YES;
            [insertPaths addObject:path];
            [posts insertObject:entry atIndex:currentPostIndex];
        } else {
            // Find its current position in the array.
            NSUInteger foundAt = [posts indexOfObject:entry];
            if (foundAt == currentPostIndex) {
                // Reload it if it has changed.
                if (entry.savedState != PostModelSavedStateUnmodified) {
                    modified = YES;
                    [posts replaceObjectAtIndex:foundAt withObject:entry];
                    [reloadPaths addObject:[NSIndexPath indexPathForRow:num.intValue inSection:0]];
                }
            } else {
                // Move it.
                modified = YES;
                [posts removeObjectAtIndex:foundAt];
                [posts insertObject:entry atIndex:currentPostIndex];
                [insertPaths addObject:path];
                [deletePaths addObject:[NSIndexPath indexPathForRow:num.intValue inSection:0]];
            }
        }
        currentPostIndex++;
    }
    if (modified) {
        [tableView beginUpdates];
        [tableView insertRowsAtIndexPaths:insertPaths withRowAnimation:UITableViewRowAnimationTop];
        [tableView deleteRowsAtIndexPaths:deletePaths withRowAnimation:UITableViewRowAnimationBottom];
        [tableView reloadRowsAtIndexPaths:reloadPaths withRowAnimation:UITableViewRowAnimationFade];
        [tableView endUpdates];
    }
    
    欢迎评论。完全有可能有更有效的方法来做到这一点(使用
    -[NSArray indexOfObject:
    对我来说尤其可疑),而且我可能错过了其他一些微妙之处。