NSFetchedResultsController未加载所有entires

时间:2015-11-18 16:12:13

标签: ios iphone uitableview core-data nsfetchedresultscontroller

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

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

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

我的核心数据模型如下:

enter image description here

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

- (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 = @“langauges tab”;

    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];
}

我从fetchedResultsController拨打viewDidLoad并从viewWillAppear重新加载表格。

然而,Languages标签上的情况是它只显示我添加的最后一种语言,每次启动时,我都会创建另一个新的空单元格。

enter image description here

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

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

任何指导都会非常感激。

2 个答案:

答案 0 :(得分:1)

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

Apple doc似乎也表明了这一点:

  

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

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;

}

答案 1 :(得分:1)

我认为您需要优化数据模型:

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

其次,为什么Language实体上有一个language实体,一个Video属性?从您的代码中,language属性看起来像是表示语言名称的字符串。我建议你应该在name实体上设置Language属性。

除了这些要点之外,问题的关键还在于loadVideosTab方法。它只创建一个Language对象:

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

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

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

但是,由于videos关系是" to-one",Language对象只能与一个Video相关联。因此,每次设置关系时,都会删除旧关系。在方法结束时,您的Language只与Tamil Video对象相关。其他七个Video个对象与任何Language无关。

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

修改

关于处理多对多关系的注释:该关系表示为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可以有多个Language s?