NSFetchedResultsController 在启动后不更新 UITableView

Posted

技术标签:

【中文标题】NSFetchedResultsController 在启动后不更新 UITableView【英文标题】:NSFetchedResultsController doesn't update UITableView after launch 【发布时间】:2013-10-16 02:40:01 【问题描述】:

我有一个简单的基于 UIManagedDocument 的核心数据应用程序。我是在 ios 中编程的新手,我几乎没有开始使用它,我被困住了。我将 People NSManagedObjects 存储在核心数据中。当我第一次启动应用程序时,那里没有任何条目,但是如果我向上滑动一个模态 VC 并关闭它,它们就会神奇地出现。我的设计大致基于 iTunes U/Sanford 课程中的“Photomania”应用程序,但使用单例 CoreDataStore 对象来处理获取 managedObjectContext。这是我的 CoreDataStore 的代码:

+ (CoreDataStore *)sharedStore

    static CoreDataStore *sharedStore = nil;
    if (!sharedStore)
        sharedStore = [[super allocWithZone:nil] init];

    return sharedStore;


+ (instancetype)allocWithZone:(struct _NSZone *)zone

    return [self sharedStore];


- (NSManagedObjectContext *)getContext

    NSURL *url = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory
                                                         inDomains:NSUserDomainMask] lastObject];
    url = [url URLByAppendingPathComponent:@"dbDocument"];
    UIManagedDocument *document = [[UIManagedDocument alloc] initWithFileURL:url];

    if (![[NSFileManager defaultManager] fileExistsAtPath:[url path]]) 
        NSLog(@"No file exists...");
        [document saveToURL:url forSaveOperation:UIDocumentSaveForCreating completionHandler:^(BOOL success) 
            NSLog(@"Document saveForCreating completionHandler invoked. Success: %hhd", success);
            if (success) 
                self.managedObjectContext = document.managedObjectContext;
                NSLog(@"Got context for created file!!");
            
        ];
     else if (document.documentState == UIDocumentStateClosed) 
        NSLog(@"Document state is closed. documentState == %u", document.documentState);
        [document openWithCompletionHandler:^(BOOL success) 
            NSLog(@"Document opening success: %hhd", success);
            if (success) 
                self.managedObjectContext = document.managedObjectContext;
                NSLog(@"Got context for now-opened file!!");
            
        ];
     else 
        self.managedObjectContext = document.managedObjectContext;
        NSLog(@"Got context for already-opened file!!");
    

    return self.managedObjectContext;

这是来自 PeopleListViewController 的代码:

- (void)setManagedObjectContext:(NSManagedObjectContext *)managedObjectContext

    _managedObjectContext = managedObjectContext;
    if (managedObjectContext) 
        NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Person"];
        request.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"name"
                                        ascending:YES
                                        selector:@selector(localizedCaseInsensitiveCompare:)]];
        request.predicate = nil;  // all Persons
        self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:nil];
     else 
        self.fetchedResultsController = nil;
    


- (void)viewWillAppear:(BOOL)animated

    [super viewWillAppear:animated];
    if (!self.managedObjectContext)
        [self setManagedObjectContext:[[CoreDataStore sharedStore] getContext]];

这是启动后的结果:

这是模态VC:

这是在关闭模态 VC 之后(点击取消):

我尝试在 managedObjectContext 上使用 performFetch,甚至将其延迟到 Core Data 文档报告它已打开之后。没有任何效果。任何帮助将不胜感激。

【问题讨论】:

【参考方案1】:

粗略浏览一下您的代码,我注意到您正在异步创建上下文。这很好,但是当上下文准备好时,您需要通知表视图控制器并让它调用 reloadData。

数据存储中的完成处理程序需要在表视图控制器上设置上下文属性。

【讨论】:

我该怎么做。我确定这是需要在完成处理程序中进行的操作,但我不确定该怎么做。这听起来可能是问题所在。我做了一个测试,发现[fetchedObjects count]最初是0,然后在关闭模态VC后是5。【参考方案2】:

NSFetchedResultsController 不会神奇地更新您的 tableView。我发现 Photomania 有点复杂,因为像 CoreDataViewController 这样的辅助类很少。因此 tableVew 只能通过调用 reloadData 方法或使用一对 beginUpdates-endUpdates 重新加载。而NSFetchedResultsController 只能通过调用performFetch: 方法来更新。 我一般用NSManagedObjectContextgetter:

- (NSFetchedResultsController*)fetchedResultsController

    if (!_fetchedResultsController)
    
        NSFetchRequest* fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"some entity name"];
        fetchRequest.predicate = [NSPredicate predicateWithFormat:@"some predicate"];
        fetchRequest.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES]];
        _fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.context sectionNameKeyPath:@"name" cacheName:nil];
        _fetchedResultsController.delegate = self;
        NSError* error;
        [_fetchedResultsController performFetch:&error];
        if (error)
        
            NSLog(@"Fetch ERROR. %@", error);
        
    
    return _fetchedResultsController;

在这里我将NSFetechedResultsControllerDelegate 设置为self。和performFetch: 获取数据。我实现了委托的方法:

- (void)controllerWillChangeContent:(NSFetchedResultsController*)controller

    [self.tableView beginUpdates];


- (void)controller:(NSFetchedResultsController*)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
           atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type

    switch(type)
    
        case NSFetchedResultsChangeInsert:
            [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
                          withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeDelete:
            [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex]
                          withRowAnimation:UITableViewRowAnimationFade];
            break;
    



- (void)controller:(NSFetchedResultsController*)controller didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath*)indexPath forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath*)newIndexPath

    UITableView *tableView = self.tableView;

    switch(type)
    
        case NSFetchedResultsChangeInsert:
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                             withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeDelete:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                             withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeUpdate:
            [self configureCell:[tableView cellForRowAtIndexPath:indexPath]
                    atIndexPath:indexPath];
            break;

        case NSFetchedResultsChangeMove:
            [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                             withRowAnimation:UITableViewRowAnimationFade];
            [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                             withRowAnimation:UITableViewRowAnimationFade];
            break;
    


- (void)controllerDidChangeContent:(NSFetchedResultsController*)controller

    [self.tableView endUpdates];

【讨论】:

我在viewWillAppear后面加了一个performFetch:,但是结果是一样的。 beginUpdates/endUpdates 不起作用,因为它是应用程序启动的时候。未进行任何更新。 @LouValencia,尝试在fetchedResultsController setter 中使用performFetch:。所以你会在必要时performFetch:,例如numberOfRowsInSection:。如果您的存储发生变化,NSFetchedResultsControllerDelegate 方法将更新您的 tableView

以上是关于NSFetchedResultsController 在启动后不更新 UITableView的主要内容,如果未能解决你的问题,请参考以下文章

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