NSFetchedResultsController 刷新 UITableView 条目而不重新加载口吃

Posted

技术标签:

【中文标题】NSFetchedResultsController 刷新 UITableView 条目而不重新加载口吃【英文标题】:NSFetchedResultsController refresh UITableView entries without reload stutter 【发布时间】:2012-02-07 02:25:04 【问题描述】:

我有一个由 NSFetchedResultsController 驱动的 UITableView。我调用了一个方法来创建一个 NSPredicate 来过滤从今天开始具有时间戳的我的实体。在我的 NSFetchedResultsController 中,我应用了谓词。

if (appDelegate.displayTodayOnly) 
    [fetchRequest setPredicate:datePredicate];

这一切都很好。但是,有时我需要查看“今天”之前的条目。当它已经在表格的开头时,我通过在表格视图上向下滑动来做到这一点。

-(void) scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate   
    if (self.tableView.contentOffset.y<0) 
        if (appDelegate.displayTodayOnly) 
            [appDelegate setDisplayTodayOnly:NO];
            NSInteger resultsOffset = [[self.fetchedResultsController fetchedObjects] count];
            //[self.tableView beginUpdates];
            [__fetchedResultsController release];
            __fetchedResultsController=nil;
            NSError *error;
            if (![[self fetchedResultsController] performFetch:&error]) 
                NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
                exit(-1);  // Fail
            
            //[self.tableView endUpdates];
            NSInteger fullCount = [[self.fetchedResultsController fetchedObjects] count];
            if (fullCount>resultsOffset) 
                NSUInteger sect = [[self.fetchedResultsController sections] count]-2;
                id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:sect];
                NSUInteger rows = [sectionInfo numberOfObjects]-1;
                NSIndexPath *indexing = [NSIndexPath indexPathForRow:rows inSection:sect];
                NSLog(@"IndexPath = section:%d row:%d",sect,rows);
                [self.tableView reloadData];
                [self performSelector:@selector(scrollToIndexPath:) withObject:indexing afterDelay:0.0001];
            
        
    

这也有效,但不优雅。它丢弃 fetchedResultsController,并重新加载表,从与条目相关的时间开始的条目开始。然后,我让它滚动到上一个表条目上方的索引路径。

我希望在当前表顶部之前加载和插入条目。有谁知道一种优雅的方式来改变 fetch 以实现这一点?

【问题讨论】:

【参考方案1】:

我通常做的是设置

self.fetchedResultsController = nil; 

并重新加载。它非常高效并且代码最少(假设您像在 Xcode 模板中一样懒惰地加载和创建获取的结果控制器)。

【讨论】:

是的,它有效,这就是我的 scrollViewDidEndDragging 方法中发生的事情。我试图了解是否有办法减轻重新加载,以免结果跳来跳去。【参考方案2】:

我发布我的解决方案,希望它可以帮助其他有需要的人。

我仍在将 NSFetchedResultsController 实例设置为 nil,并获得一个具有不同谓词的新实例。然后,我没有重新加载表格(导致迷失方向的跳转),而是在 for(loop) 中手动调用[self.tableView insertSections:[NSIndexSet indexSetWithIndex:i] withRowAnimation:UITableViewRowAnimationNone];,它几乎直接来自&lt;NSFetchedResultsControllerDelegate&gt; 方法的样板文件controllerWillChangeContent:。这是有效的,因为最后一部分总是今天的部分。如果今天没有任何条目,我需要重新加载数据,因为插入的节数会不一致,因为今天的空节算作一个节。

-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate
   
    if (self.tableView.contentOffset.y<0) 
        if (appDelegate.displayTodayOnly) 
            [appDelegate setTodayOnlyMemory:NO];
            NSUInteger resultsOffset = [[self.fetchedResultsController fetchedObjects] count];
           [__fetchedResultsController release];
            __fetchedResultsController = nil;
            self.fetchedResultsController = nil;

            NSError *error = nil;
            if (![self.fetchedResultsController performFetch:&error])
            
                NSLog(@"error in fetched results controller of Memory Notes");
                NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
                abort();
            
            if (resultsOffset) 
                [self.tableView beginUpdates];
                NSInteger afCount = ([[self.fetchedResultsController sections] count]-1);
                for (NSInteger i=0; i<afCount; i++) 
                    [self.tableView insertSections:[NSIndexSet indexSetWithIndex:i] withRowAnimation:UITableViewRowAnimationNone]; 
                
                [self.tableView endUpdates];
             else 
                [self.tableView reloadData];
                        
            [self performSelector:@selector(scrollToSpot) withObject:nil afterDelay:0.01f];
        
    

此外,这是我的 scrollToSpot 方法,用于在正确的位置结束。

-(void)scrollToSpot
    
    NSArray *sectArray = [self.fetchedResultsController sections];
    if ([sectArray count]>1) 
        id <NSFetchedResultsSectionInfo> sectionInfo = [sectArray objectAtIndex:([sectArray count]-2)];
        NSInteger theRow = [sectionInfo numberOfObjects]-1;
        NSIndexPath* ipath = [NSIndexPath indexPathForRow:theRow inSection:([sectArray count]-2)];
        NSLog(@"Row:%d",ipath.row);
        [self.tableView scrollToRowAtIndexPath:ipath atScrollPosition:UITableViewScrollPositionTop animated:NO];
        NSLog(@"ContentOffset:%.0f",self.tableView.contentOffset.y);
    

【讨论】:

以上是关于NSFetchedResultsController 刷新 UITableView 条目而不重新加载口吃的主要内容,如果未能解决你的问题,请参考以下文章

在 Core Data 应用程序中调用 performFetch 后,是不是需要手动更新表视图?