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

the*_*ory 19 animation cocoa-touch feed uitableview

我的iPad应用程序具有从Feed中填充的UITableView.与大多数RSS阅读器一样,它以反向时间顺序显示博客帖子的链接列表,其标题和每个帖子的摘要.Feed经常更新,并且非常大,大约有500个帖子.我正在使用libxml2推送解析来有效地下载和解析NSOperation子类中的feed,构建入口对象并随时更新数据库.但后来我需要更新UITableView.

到目前为止,应用程序一直在为解析的每个帖子更新UITableView.解析器在主线程上执行选择器来完成这项工作.但是如果需要更新很多细胞,这会导致几秒钟的严重滞后.我可以通过在后台线程上运行表更新来缓解这种情况,但似乎这不是一个好主意.所以现在我想弄清楚如何在主线程上更有效地更新表.

我可以reloadData在解析所有帖子时调用,但它不是非常用户友好:没有动画来指示任何内容已经发生变化,只是一个闪存并且新数据就在那里.我宁愿让它动画显示添加新帖子和删除旧帖子.未从Feed中删除的现有帖子应该通过顶部显示的新帖子向下推.

我知道这是可能的.举个例子,Byline做得很漂亮.每个帖子一次一个地添加或删除UITableView,没有显示表格背景的间隙.所有这一切都没有让UI在一点也不响应.怎么办?

我的最新尝试是仅在解析了所有帖子之后更新表(解析器非常快,所以它没有太大的延迟).然后,它将现有帖子加载到NSDictionary中,将其ID映射到用作表数据源的数组中的索引.然后,它的每一个对象遍历柱的新解析阵列中,加入NSIndexPath对于每个到稍后传递到阵列-insertRowsAtIndexPaths:withRowAnimation:,-deleteRowsAtIndexPaths:withRowAnimation:以及-reloadRowsAtIndexPaths:withRowAnimation:酌情插入,删除,移动,或更新的细胞.对于500个帖子,这需要大约4秒才能更新,UI完全没有响应.那个时间几乎专门用于UITableView动画更新; 迭代两个帖子数组只需要很少的时间.

我然后修改它,以便更新它们没有动画,我有单独的数组插入/删除/重新加载动画仅适用于对应于当前可见行的行位置.这样做会更好,但是会删除帖子并添加新帖子.

对不起,这是如此啰嗦,但这是结果:

如何更新UITableView,推送新单元格,推送其他单元格,还有其他人从一个位置移动到另一个位置,UITableView中最多有500个单元格(一次可见6-8个),每个动画都会发生按顺序,UI仍然完全响应?

the*_*ory 33

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

  1. 如何保持UI响应
  2. 如何快速保持更新
  3. 如何使表更新动画流畅

UI响应

为了解决第一个问题,我现在确保在主事件循环的每次迭代中只能传递一个表更新消息.这可以防止主线程锁定,如果后台线程正在以比它可以处理它更快的速度进行操作.

这样做是要归功于发送到我的示例代码署名作者米洛鸟,然后我融入戴夫DribinDDInvocationGrabber.此接口使得在主事件循环的下一个可用迭代上调用方法变得非常容易:

[[(id)delegate queueOnMainThread]
    parserParsedEntries:parsedEntries
               inPortal:parsedPortal];
Run Code Online (Sandbox Code Playgroud)

我非常喜欢使用这种方法是多么容易.解析器现在使用它来调用所有委托方法,其中大多数方法都会更新UI.我已经在GitHub上发布了这段代码.

性能

至于性能,我最初一次更新一个UITableView行.这是有效的,但效率低下.我回去研究了XMLPerformance示例,在那里我注意到解析器在调度到主线程以更新表之前一直等到收集了10个项目.这是保持性能提升的关键,而不是通过一次更新所有500行来锁定UI.我在一次调用中更新了1个,10个和所有500行,而更新10似乎提供了性能和UI锁定之间的最佳权衡.五个也可能工作得很好.

动画

最后,还有动画.看着"掌握表格视图"WWDC 2010会话,我意识到我对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];
}
Run Code Online (Sandbox Code Playgroud)

欢迎评论.完全有可能有更有效的方法来做到这一点(使用-[NSArray indexOfObject:]对我来说特别可疑),而且我可能错过了其他一些微妙之处.

但即便如此,这对我的应用来说也是一个巨大的进步.UI现在(大部分)在同步期间保持响应,同步很快,并且表更新动画看起来恰到好处.