2

J'ai un objet géré avec un attribut dueDate. Au lieu d'afficher en utilisant une chaîne de date laid comme les en-têtes de section de mon UITableView j'ai créé un attribut transitoire appelé « catégorie » et défini comme ceci:Nom de section personnalisé Crashing NSFetchedResultsController

- (NSString*)category 
{ 
    [self willAccessValueForKey:@"category"]; 

    NSString* categoryName; 
    if ([self isOverdue]) 
    { 
     categoryName = @"Overdue"; 
    } 
    else if ([self.finishedDate != nil]) 
    { 
     categoryName = @"Done"; 
    } 
    else 
    { 
     categoryName = @"In Progress"; 
    } 

    [self didAccessValueForKey:@"category"]; 
    return categoryName; 
} 

Voici le NSFetchedResultsController mis en place:

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; 
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Task" 
              inManagedObjectContext:managedObjectContext]; 
[fetchRequest setEntity:entity]; 

NSMutableArray* descriptors = [[NSMutableArray alloc] init]; 
NSSortDescriptor *dueDateDescriptor = [[NSSortDescriptor alloc] initWithKey:@"dueDate" 
                    ascending:YES]; 
[descriptors addObject:dueDateDescriptor]; 
[dueDateDescriptor release]; 
[fetchRequest setSortDescriptors:descriptors]; 

fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:@"category" cacheName:@"Root"]; 

La table affiche initialement bien, montrant les éléments inachevés dont la date d'échéance n'a pas passé dans une section intitulée "En cours". Maintenant, l'utilisateur peut appuyer sur une rangée dans la vue de table qui pousse une nouvelle vue de détails sur la pile de navigation. Dans cette nouvelle vue, l'utilisateur peut appuyer sur un bouton pour indiquer que l'élément est maintenant "Terminé". Voici le gestionnaire pour le bouton (self.task est l'objet géré):

- (void)taskDoneButtonTapped 
{ 
    self.task.finishedDate = [NSDate date]; 
} 

Dès que la valeur de l'attribut « finishedDate » changements que je suis touché à cette exception:

2010-03-18 23:29:52.476 MyApp[1637:207] Serious application error. Exception was caught during Core Data change processing: no section named 'Done' found with userInfo (null) 
2010-03-18 23:29:52.477 MyApp[1637:207] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'no section named 'Done' found' 

J'ai réussi à comprendre que le UITableView qui est actuellement masqué par la nouvelle vue de détails essaye de mettre à jour ses rangées et sections parce que le NSFetchedResultsController a été notifié que quelque chose a changé dans l'ensemble de données. Voici mon code de mise à jour de la table (copiée à partir soit de l'échantillon Recettes données de base ou l'échantillon CoreBooks - je ne me souviens pas qui):

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller 
{ 
    [self.tableView beginUpdates]; 
} 

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath 
{ 
    switch(type) 
    { 
     case NSFetchedResultsChangeInsert: 
      [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; 
      break; 

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

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

     case NSFetchedResultsChangeMove: 
      [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; 
      // Reloading the section inserts a new row and ensures that titles are updated appropriately. 
      [self.tableView reloadSections:[NSIndexSet indexSetWithIndex:newIndexPath.section] withRowAnimation:UITableViewRowAnimationFade]; 
      break; 
    } 
} 

- (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)controllerDidChangeContent:(NSFetchedResultsController *)controller 
{ 
    [self.tableView endUpdates]; 
} 

je mets des points d'arrêt dans chacune de ces fonctions et a constaté que seulement controllerWillChange est appelé . L'exception est levée avant chaque contrôleur: didChangeObject: atIndexPath: forChangeType: newIndex ou contrôleur: didChangeSection: atIndex: forChangeType sont appelés.

À ce stade, je suis coincé. Si je change mon sectionNameKeyPath à juste "dueDate" alors tout fonctionne bien. Je pense que c'est parce que l'attribut dueDate ne change jamais alors que la catégorie sera différente lors de la relecture après l'attribut finishedDate.

Aidez s'il vous plaît!

MISE À JOUR:

Voici mon code UITableViewDataSource:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 
{ 
    return [[self.fetchedResultsController sections] count]; 
} 

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 
{ 
    id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section]; 
    return [sectionInfo numberOfObjects]; 
} 

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 
{ 
    static NSString *CellIdentifier = @"Cell"; 

    UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier]; 
    if (cell == nil) 
    { 
     cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease]; 
    } 

    [self configureCell:cell atIndexPath:indexPath];  

    return cell; 
} 

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section 
{ 
    id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section];  
    return [sectionInfo name]; 
} 

Répondre

1

Il me semble que votre problème est lié à votre "catégorie" propriété transient que vous utilisez pour alimenter le sectionNameKeyPath. Le sectionNameKeyPath doit commander le même que le descripteur de tri principal. Dans votre cas, cela signifie que toutes les tâches "en retard" DOIVENT avoir des dates plus tôt que toutes les tâches "Terminées" DOIVENT avoir des dates plus tôt que toutes les tâches "En cours". Il est possible de construire un scénario dans lequel une tâche "Done" a un "DueDate" qui vient après une tâche "En cours" ou vient avant une tâche "En retard". Ce scénario brise l'exigence d'ordre de sectionNameKeyPath et provoque le NSFetchedResultsController à lancer une exception NSInternalConsistencyException.

Je propose une solution à votre problème qui n'implique pas de rouler votre propre tableau qui doit ensuite être divisé en sections. Créez un attribut entier dans votre modèle où vous mappez 0 à "En retard", 1 à "Terminé" et 2 à "En cours". Faites-en le principal descripteur de tri dans votre NSFetchRequest et triez cette propriété dans l'ordre croissant. Ajoutez un descripteur de tri secondaire à NSFetchRequest qui trie la propriété dueDate dans l'ordre croissant.Modifiez la méthode de votre catégorie pour dériver les noms de catégorie à partir de l'attribut entier que vous avez créé ci-dessus et utilisez-le comme sectionNameKeyPath. Vous devrez mettre à jour l'attribut entier pour mettre à jour les tâches en cours de progression à en cours, etc.

+1

Je suis respectueusement en désaccord avec Marcus - je ne pense pas que ce soit un bug du tout. La documentation de NSFetchedResultsController indique: "La requête d'extraction doit avoir au moins un descripteur de tri.Si le contrôleur génère des sections, le premier descripteur de tri du tableau est utilisé pour regrouper les objets en sections, sa clé doit être la même que sectionNameKeyPath ou l'ordre relatif en utilisant sa clé doit correspondre à celui utilisant sectionNameKeyPath. " Comme je le vois, le crash se produit parce que la clé pour le descripteur de tri et la clé pour le sectionNameKeyPath ont des ordres de tri différents. – glorifiedHacker

+0

Suggestion très intéressante. Fait sens en fonction de ce que j'ai lu dans les documents du SDK. Je suis déjà allé la voie d'avoir un tableau séparé, mais je vais voir si je peux essayer à un moment donné. Merci! –

2

Le crash est provoqué par le NSFetchedResultsController ne connaissant pas la catégorie "done" avant la main et donc le plantage. J'ai vu ce crash quelques autres fois dans d'autres questions et avec chacun je recommande de soumettre un ticket radar à Apple. Ceci est un bug dans le NSFetchedResultsController.

+0

Juste trébuché sur cette question parce que mon application se bloque sous iOS 3 exactement pour cette raison, mais pas sous iOS 4. Devra trouver une solution de contournement ou supprimer le soutien 3.x ... – Pascal