【问题标题】:NSFetchedResultsController not loading all entiresNSFetchedResultsController 未加载所有条目
【发布时间】:2016-02-20 11:29:00
【问题描述】:

我有一个简单的应用程序,每个 UITabBar 中有一个 UITabBarController 和 2 个 UITableViewControllers。第一个UITableViewController 称为Videos,第二个称为Languages

Videos 将仅代表具有视频的语言,Languages 将显示所有语言。

出于测试目的,我从 AppDelegate 填充核心数据实体。

我的核心数据模型如下:

填充核心数据的方法如下所示:

- (void)loadVideosTab
{
    // Here is where we'll load up the Videos tab.
    NSManagedObjectContext *context = [self managedObjectContext];
    NSString *chinese = @"Chinese";
    NSString *dutch = @"Dutch";
    NSString *english = @"English";
    NSString *french = @"French";
    NSString *italian = @"Italian";
    NSString *punjabi = @"Punjabi";
    NSString *spanish = @"Spanish";
    NSString *tamil = @"Tamil - தமிழ்";

    Language *language = [NSEntityDescription insertNewObjectForEntityForName:@"Language" inManagedObjectContext:context];


    Video *chineseLanguage = (Video *)[Video videoLanguage:chinese inManagedObjectContext:context];
    language.videos = chineseLanguage;
    Video *dutchLanguage = (Video *)[Video videoLanguage:dutch inManagedObjectContext:context];
    language.videos = dutchLanguage;
    Video *englishLanguage = (Video *)[Video videoLanguage:english inManagedObjectContext:context];
    language.videos = englishLanguage;
    Video *frenchLanguage = (Video *)[Video videoLanguage:french inManagedObjectContext:context];
    language.videos = frenchLanguage;
    Video *italianLanguage = (Video *)[Video videoLanguage:italian inManagedObjectContext:context];
    language.videos = italianLanguage;
    Video *punjabiLanguage = (Video *)[Video videoLanguage:punjabi inManagedObjectContext:context];
    language.videos = punjabiLanguage;
    Video *spanishLanguage = (Video *)[Video videoLanguage:spanish inManagedObjectContext:context];
    language.videos = spanishLanguage;
    Video *tamilLanguage = (Video *)[Video videoLanguage:tamil inManagedObjectContext:context];
    language.videos = tamilLanguage;

    NSError *error = nil;
    if (![context save:&error])
    {
        // Error
    }    
}

我在 Video 实体上调用的类别是:

+ (Video *)videoLanguage:(NSString *)name inManagedObjectContext:(NSManagedObjectContext *)context
{
    Video *video = nil;

    // Creating a fetch request to check whether the video already exists, calling from the AppDelegate
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Video"];
    request.predicate = [NSPredicate predicateWithFormat:@"language = %@", name];
    NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"language" ascending:YES];
    request.sortDescriptors = [NSArray arrayWithObject:sortDescriptor];

    NSError *error = nil;
    NSArray *videoTitles = [context executeFetchRequest:request error:&error];
    if (!videoTitles)
    {
        // Handle Error
    }
    else if (![videoTitles count])
    {
        // If the video count is 0 then create it
        video = [NSEntityDescription insertNewObjectForEntityForName:@"Video" inManagedObjectContext:context];
        video.language = name;

    }
    else
    {
        // If the object exists, just return it
        video = [videoTitles lastObject];
    }
    return video;

}

所以在Video 标签中,我使用的是NSFetchedResultsController

- (NSFetchedResultsController *)fetchedResultsController
{
    NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
    if (_fetchedResultsController != nil)
    {
        return _fetchedResultsController;
    }
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];


    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Video" inManagedObjectContext:managedObjectContext];
    fetchRequest.entity = entity;
    NSPredicate *d = [NSPredicate predicateWithFormat:@"language != nil"];
    [fetchRequest setPredicate:d];
    NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:@"language" ascending:YES];


    fetchRequest.sortDescriptors = [NSArray arrayWithObject:sort];
    fetchRequest.fetchBatchSize = 20;
    NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:nil];
    self.fetchedResultsController = theFetchedResultsController;
    _fetchedResultsController.delegate = self;
    return _fetchedResultsController;
}

cellForRow 是:

VideoTabTableViewCell *customCell = (VideoTabTableViewCell *)cell;

Video *video = [self.fetchedResultsController objectAtIndexPath:indexPath];

customCell.videoTabCellLabel.text = video.language;
customCell.videoTabCellLabel.textColor = [UIColor whiteColor];

这成功地引入了语言。

现在,出于测试目的,我希望“语言”选项卡执行完全相同的操作。我有一个Language 实体的原因是“语言”选项卡将是具有视频和传单的语言的组合。

所以在Languages 标签中,我的fetchedResultsController 是:

- (NSFetchedResultsController *)fetchedResultsController
{
    NSManagedObjectContext *managedObjectContext = [self managedObjectContext];
    if (_fetchedResultsController != nil)
    {
        return _fetchedResultsController;
    }
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];


    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Language" inManagedObjectContext:managedObjectContext];
    fetchRequest.entity = entity;
//    NSPredicate *d = [NSPredicate predicateWithFormat:@"name != nil"];
//    [fetchRequest setPredicate:d];
    NSSortDescriptor *sort = [[NSSortDescriptor alloc] initWithKey:@"videos.language" ascending:YES];


    fetchRequest.sortDescriptors = [NSArray arrayWithObject:sort];
    fetchRequest.fetchBatchSize = 20;
    NSFetchedResultsController *theFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:nil];
    self.fetchedResultsController = theFetchedResultsController;
    _fetchedResultsController.delegate = self;
    return _fetchedResultsController;
}

cellForRow 是:

VideoTabTableViewCell *customCell = (VideoTabTableViewCell *)cell;

Language *languages = [self.fetchedResultsController objectAtIndexPath:indexPath];

customCell.videoTabCellLabel.text = languages.videos.language;
customCell.videoTabCellLabel.textColor = [UIColor whiteColor];

更新 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"语言标签";

    VideoTabTableViewCell *cell = (VideoTabTableViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];

    [self configureCell:cell atIndexPath:indexPath];
    return cell;

}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    id  sectionInfo = [[_fetchedResultsController sections] objectAtIndex:section];
    return [sectionInfo numberOfObjects];
}
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
    [self.languagesTabTableView beginUpdates];
}

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath
{
    // The boiler plate code for the NSFetchedResultsControllerDelegate

    UITableView *tableView = self.languagesTabTableView;

    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;

        default:
            break;

    }
}

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
    // The fetch controller has sent all current change notifications, so tell the table view to process all updates.
    [self.languagesTabTableView endUpdates];
}

我从viewDidLoad 调用fetchedResultsController 并从viewWillAppear 重新加载表。

但是,Languages 选项卡上的情况是它只显示我添加的最后一种语言,并且每次启动时,我都会创建另一个新的空单元格。

我只是无法弄清楚这到底是怎么回事。

澄清一下,这里的主要问题不是幻影细胞,而是为什么语言只拾取最后一个条目而不是其他条目。

任何指导将不胜感激。

【问题讨论】:

  • 几个 cmets:1. 我在 cellforrowatindex 处缺少出队... 2. 单元格是否可能是空的,因为它们为空 3. 您是否缺少 NSFetchedResultsController 中的数据或它只是没有显示 4. 放一些 nslogs 并检查上述内容,并在相关区域放置一些断点并执行 po
  • 非常感谢@RoyFalk 的回复 - 我很感激。我没有完全展示单元格的代码(对此感到抱歉)和 NSFRC 也是如此。我在更新中提供了这一点。我用languages.videos.language 在cellForRow 中放置了一个NSLog,结果是NULL、NULL,然后是泰米尔语。此类与“视频”选项卡具有相同的代码,除了 NSFRC 不同并且 cellForRow 指向 language.video.language 而不是 video.language..

标签: ios iphone uitableview core-data nsfetchedresultscontroller


【解决方案1】:

我认为你需要改进你的数据模型:

首先,从LanguageVideo 的关系有一个复数名称videos,这表明您希望每个Language 对象与许多Video 对象相关联。但是模型编辑器中的图像显示该关系当前定义为“对一”。

其次,为什么在Video 实体上有一个Language 实体,一个language 属性?从您的代码来看,language 属性似乎是一个表示语言名称的字符串。我建议您改为在 Language 实体上使用 name 属性。

除了这些点之外,您的问题的症结在于您的loadVideosTab 方法。它只创建一个Language 对象:

Language *language = [NSEntityDescription insertNewObjectForEntityForName:@"Language" inManagedObjectContext:context];

然后创建(或获取)几个Video对象,并与(一个)Language建立关系,例如:

Video *chineseLanguage = (Video *)[Video videoLanguage:chinese inManagedObjectContext:context];
language.videos = chineseLanguage;

但是,因为videos 关系是“一对一”,所以Language 对象只能与一个Video 相关联。因此,每次设置关系时,旧的都会被删除。在您的方法结束时,您的 Language 仅与泰米尔语 Video 对象相关。其他七个Video 对象与任何Language 都不相关。

每次运行都会创建一个新的Language,并与泰米尔语Video 建立关系。因为每个Video 只能有一个Language,当建立这种关系时,CoreData 将泰米尔语视频与“旧”Language 对象的关系删除。因此,您最终会得到几个与任何 Video 无关的“旧”Language 对象 - 这些是语言表视图中的空白行。

编辑

关于处理多对多关系的说明:关系表示为 NSSet。如果您正在设置关系(并且想要删除现有关系),您可以使用:

language.videos = [NSSet setWithObject:tamilLanguage];

或者一次设置多个对象,使用:

language.videos = [NSSet setWithArray:@[chineseLanguage, dutchLanguage, tamilLanguage]];

这些破坏了现有的关系,所以要添加一个关系,同时保持现有的关系,使用NSMutableSet

NSMutableSet *videos = [language mutableSetValueForKey:@"videos"];
[videos addObject:tamilLanguage];

(对于removeObject 也类似,只删除一个相关对象)。我发现这些方法相当不直观,所以如果我有一个反向是“对一”的关系,我会使用它:

tamilLanguage.videoLanguage = 语言;

话虽如此,我现在想知道这种反向关系是否也应该是“对多”的 - Video 可以有很多 Languages 吗?

【讨论】:

  • 亲爱的@pbasdf,这非常有帮助。我对 Core Data 模型不太自信,您的信息为我指明了许多积极的方向,对此我表示感谢。每种语言确实可以有一个或多个视频;如果我在模型中设置它,则 NSManagedObject 子类具有 作为视频实体的要求。如何在 loadVideosTab 方法中以相同或相似的方式适当地使用它来添加数据?再次感谢!
  • 抱歉,澄清一下,使用 to-many 创建 @property (nullable, nonatomic, retain) NSSet
  • 通过一些工作,我设法完成了这项工作,但您为我指明了正确的方向 - 非常感谢!
【解决方案2】:

我不熟悉托管对象,但这看起来就像一本字典。多个同名的 insertNewObjectForEntityForName 意味着最后一个会覆盖其余的。

Apple 文档似乎也表明了这一点:

讨论
此方法使您可以轻松创建给定实体的实例,而无需担心托管对象创建的细节。该方法在概念上类似于以下代码示例。

NSManagedObjectModel *managedObjectModel =
        [[context persistentStoreCoordinator] managedObjectModel];
NSEntityDescription *entity =
        [[managedObjectModel entitiesByName] objectForKey:entityName];
NSManagedObject *newObject = [[NSManagedObject alloc]
            initWithEntity:entity insertIntoManagedObjectContext:context];
return newObject;

注意上面的objectForKey!与 NSDictionary 相同

================================================ ==================

dequeueReusableCellWithIdentifier 并不总是返回非零对象。它用于重用现有的单元格。但如果没有可用的,则需要分配/初始化一个。

Jay Versluis致敬

类似:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath   {
  UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"Cell"];
  if (cell == nil) {
     cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"Cell"];
  }

  // populate your cell
  return cell;

}

【讨论】:

  • 嗨@RoyFalk - 非常感谢你,这很有趣。我将部署它。我在创建核心数据项的方法中添加了一个 NSLog,并且只有“泰米尔语”出现 - 为什么它不注册其余语言,而只注册最后一个插入的语言,当 Videos *video; video.language 引入了所有数据?它与一对一或一对多的关系有关吗?
  • @amitsbajaj 请参阅上面我修改后的答案。
  • 非常感谢您的回复,我很感激。不幸的是,我不得不根据其他答案改进我的数据模型,但你的代码真的很有帮助,所以虽然我不能接受它,但我赞成它。再次感谢!
猜你喜欢
  • 2011-04-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-10-20
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多