TableView avec deux instances de NSFetchedResultsController

Après des jours de search et de reencoding, je suis à peu près perplexe. Mon objective est de faire en sorte qu'une application de test s'exécute avec une seule vue de table remplie à partir de deux récupérateurs récupérés séparés.

J'ai une série d'articles sur une list de courses, chacun avec un département et un drapeau boolean 'collecté'. Les articles non collectés doivent être listés par département, suivis d'une section unique contenant tous les articles collectés (quel que soit le département). Lorsqu'un user coche des objects non collectés, il doit descendre dans la section "collecté". Si il / elle décoche un object collecté, il doit revenir dans son département correct.

entrez la description de l'image ici

Pour atteindre la première partie (éléments non collectés), j'ai mis en place un simple fetchedResultsController qui récupère tous les éléments où collecté = NON, et sectionné les résultats par département:

- (NSFetchedResultsController *)firstFRC { // Set up the fetched results controller if needed. if (firstFRC == nil) { // Create the fetch request for the entity. NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; // fetch items NSEntityDescription *entity = [NSEntityDescription entityForName:@"Item" inManagedObjectContext:managedObjectContext]; [fetchRequest setEntity:entity]; // only if uncollected NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(collected = NO)"]; [fetchRequest setPredicate:predicate]; // sort by name NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES]; NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil]; [fetchRequest setSortDescriptors:sortDescriptors]; // fetch results, sectioned by department NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:@"department" cacheName:nil]; aFetchedResultsController.delegate = self; self.firstFRC = aFetchedResultsController; } return firstFRC; } 

Je définis le nombre de lignes, sections et en-têtes de section comme suit:

 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { // Return the number of sections. return firstFRC.sections.count; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { // Return the number of rows in the section. return [[firstFRC.sections objectAtIndex:section] numberOfObjects]; } - (NSSsortingng *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { return [[[firstFRC sections] objectAtIndex:section] name]; } 

J'utilise également les commands standard controllerWillChangeContent, didChangeObject et controllerDidChangeContent pour append / supprimer des cellules en tant que modification de la FRC.

Par souci de brièveté, je n'inclurai pas le code d'affichage de la cellule, mais je tire essentiellement l'élément correct en fonction du path d'index de la cellule, définissez le text / sous-titre de la cellule et j'ajoute une des deux article est collecté ou non. Je câble également cette image (qui est sur un button) pour passer de cochée à non cochée lorsqu'elle est touchée, et mettre à jour l'article en conséquence.

Cette partie fonctionne très bien. Je peux voir ma list d'articles par département, et quand j'en marque un comme collecté, je le vois tomber de la list comme prévu.

Maintenant, j'ai essayé d'append la section inférieure, contenant tous les éléments collectés (dans une seule section). J'ai d'abord mis en place un second fetchedResultsConroller, cette fois pour récupérer seulement les items non collectés, et sans sectionner. J'ai également dû mettre à jour les éléments suivants:

 - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { // Return the number of sections - add one for 'collected' section return firstFRC.sections.count + 1; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { // Return the number of rows in the section if (section < firstFRC.sections.count) { return [[firstFRC.sections objectAtIndex:section] numberOfObjects]; } else { return secondFRC.fetchedObjects.count; } } - (NSSsortingng *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { if (section < firstFRC.sections.count) { return [[[firstFRC sections] objectAtIndex:section] name]; } else{ return @"Collected"; } } 

J'ai ensuite mis à jour le cellForRowAtIndexPath d'une manière similaire, de sorte que l'élément que je récupère provient de la FRC droite:

  Item *item; if (indexPath.section < firstFRC.sections.count) { item = [firstFRC objectAtIndexPath:indexPath]; } else { item = [secondFRC objectAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row inSection:0]]; } [[cell textLabel] setText:[item name]]; …rest of cell configuration 

Cela fonctionne très bien quand je lance. La vue de la table s'affiche exactement comme prévu:

entrez la description de l'image ici

Le problème (enfin)

Le (premier) problème vient quand je sélectionne la coche pour un article non collecté. Je m'attends à ce que l'article soit retiré du département dont il est question, et transféré dans la section «collecté». Au lieu de cela, j'obtiens:

CoreData: erreur: Erreur d'application grave. Une exception a été interceptée par le délégué de NSFetchedResultsController lors d'un appel à -controllerDidChangeContent: Mise à jour invalide: nombre de lignes incorrect dans la section 0. Le nombre de lignes contenues dans une section existante après la mise à jour (2) doit être égal au nombre de lignes contenues dans cette section avant la mise à jour (2), plus ou less des lignes insérées ou supprimées de cette section (1 inséré, 0 supprimé) et plus ou less le nombre de lignes déplacées dans ou hors de cette section (0 déplacé, 0 déplacé). avec userInfo (null)

Si je tente le contraire, alors je reçois une erreur différente:

* Terminaison de l'application en raison d'une exception non interceptée 'NSRangeException', raison: '* – [__ NSArrayM objectAtIndex:]: index 2 au-delà des limites [0 .. 1]'

Je soupçonne que dans les deux cas, il y a un problème de cohérence avec le nombre de sections / lignes dans les FRC et la tableview lorsqu'un élément passe d'un FRC à l'autre. Bien que cette deuxième erreur me fasse penser il y a peut-être un problème plus simple lié à ma récupération d'articles.

Toute direction ou idée serait appréciée. Je peux fournir plus de mon code si cela peut aider, et j'ai également créé une petite application de test avec une vue unique pour montrer le problème. Je peux le download si nécessaire, mais surtout je voulais tester le problème dans un bac à sable à petite échelle.

Mise à jour – code supplémentaire demandé

Comme demandé, voici ce qui se passe quand une coche est touchée:

 - (void)checkButtonTapped:(id)sender event:(id)event { NSSet *touches = [event allTouches]; UITouch *touch = [touches anyObject]; CGPoint currentTouchPosition = [touch locationInView:self.tableView]; NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint: currentTouchPosition]; if (indexPath != nil) { [self tableView: self.tableView accessoryButtonTappedForRowWithIndexPath: indexPath]; } } - (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath { Item *item; if (indexPath.section < firstFRC.sections.count) { item = [firstFRC objectAtIndexPath:indexPath]; } else { item = [secondFRC objectAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row inSection:0]]; } UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; UIButton *button = (UIButton *)cell.accessoryView; if (![item collected]) { [item setCollected:YES]; [button setBackgroundImage:[UIImage imageNamed:@"checked.png"] forState:UIControlStateNormal]; } else if ([item collected]){ [item setCollected:NO]; [button setBackgroundImage:[UIImage imageNamed:@"unchecked.png"] forState:UIControlStateNormal]; } NSError *error = nil; if (![item.managedObjectContext save:&error]) { NSLog(@"Error saving collected items"); } } 

Eh bien, je pense que je pourrais l'avoir craqué. Comme c'est souvent le cas, le dépouiller et lire quelques commentaires m'a orienté dans la bonne direction.

Lorsque j'arrive à la méthode de délégué controllerDidChangeObject, j'essaie d'insert une ligne à l'indexPath fourni (pour l'élément 'checked'). Sauf que lors de l'insertion dans ma section supplémentaire, cet indexPath n'a pas conscience du fait qu'il y a un tas d'autres sections avant. Il reçoit donc la section 0 et tente d'y insert. Au lieu de cela, si l'indexPath vient de la deuxième FRC, je devrais incrémenter le numéro de section par le nombre de sections dans la première table FRC. Donc, j'ai remplacé:

 - (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; 

avec

 - (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath { UITableView *tableView = self.tableView; switch(type) { case NSFetchedResultsChangeInsert: if ([controller.sectionNameKeyPath isEqualToSsortingng:@"department"]) { [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; } else { [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:newIndexPath.row inSection:firstFRC.sections.count]] withRowAnimation:UITableViewRowAnimationFade]; } break; 

Je devrai faire ceci pour chaque insertion, suppression, mise à jour etc. Je marquerai ceci comme réponse après que j'ai validé cela et pour laisser le time pour d'autres commentaires.

Les erreurs que vous voyez signifient que votre UITableView recharge avant que vos NSFetchedResultsController ne le fassent. Les codes que vous avez publiés sont probablement corrects. Je soupçonne que le problème est dans l'une des methods NSFetchedResultsControllerDelegate .