2009-06-29 7 views
37

Mise à jouruitableview: suppression de sections avec animation

J'ai posté ma solution à ce problème comme une réponse ci-dessous. Cela prend une approche différente de ma première révision.


Original Question J'ai déjà posé une question sur le SO que je pensais résolu mes problèmes:

How to deal with non-visible rows during row deletion. (UITableViews)

Cependant, j'ai maintenant des problèmes similaires à nouveau lors de la suppression des sections d'un UITableView. (ils ont refait surface lorsque j'ai modifié le nombre de sections/lignes dans le tableau). Avant que je ne vous perde à cause de la longueur de mon message, permettez-moi d'énoncer le problème clairement, et vous pouvez lire autant que nécessaire pour fournir une réponse.


Problème:

Si lots suppression de lignes et les sections d'un UITableView, l'application se bloque parfois. Cela dépend de la configuration de la table et de la combinaison de lignes et de sections que je choisis de supprimer.

Le journal dit, je suis tombé parce qu'il dit que je ne l'ai pas mis à jour la source de données et la table correctement:

Invalid update: invalid number of rows in section 5. The number of rows contained in an existing section after the update (2) must be equal to the number of rows contained in that section before the update (1), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted). 

maintenant rapidement, avant d'écrire la réponse évidente, je vous assure que je l'ai fait ajouter et supprimer la lignes et sections correctement à partir de la source de données. L'explication est longue, mais vous la trouverez ci-dessous, en suivant la méthode.

Donc, avec cela, si vous êtes toujours intéressé ...


méthode qui gère l'enlèvement des sections et des lignes:

- (void)createFilteredTableGroups{ 

    //index set to hold sections to remove for deletion animation 
    NSMutableIndexSet *sectionsToDelete = [NSMutableIndexSet indexSet]; 
    [sectionsToDelete removeIndex:0]; 


    //array to track cells for deletion animation 
    NSMutableArray *cellsToDelete = [NSMutableArray array]; 

    //array to track controllers to delete from presentation model 
    NSMutableArray *controllersToDelete = [NSMutableArray array]; 

    //for each section 
    for(NSUInteger i=0; i<[tableGroups count];i++){ 

     NSMutableArray *section = [tableGroups objectAtIndex:i]; 

     //controllers to remove 
     NSMutableIndexSet *controllersToDeleteInCurrentSection = [NSMutableIndexSet indexSet]; 
     [controllersToDeleteInCurrentSection removeIndex:0]; 
     NSUInteger indexOfController = 0; 

     //for each cell controller 
     for(ScheduleCellController *cellController in section){ 

      //bool indicating whether the cell controller's cell should be removed 
      NSString *shouldDisplayString = (NSString*)[[cellController model] objectForKey:@"filteredDataSet"]; 
      BOOL shouldDisplay = [shouldDisplayString boolValue]; 

      //if it should be removed 
      if(!shouldDisplay){ 

       NSIndexPath *cellPath = [self indexPathOfCellWithCellController:cellController]; 

       //if cell is on screen, mark for animated deletion 
       if(cellPath!=nil) 
        [cellsToDelete addObject:cellPath]; 

       //marking controller for deleting from presentation model 
       [controllersToDeleteInCurrentSection addIndex:indexOfController];     

      } 
      indexOfController++; 
     } 

     //if removing all items in section, add section to removed in animation 
     if([controllersToDeleteInCurrentSection count]==[section count]) 
      [sectionsToDelete addIndex:i]; 

     [controllersToDelete addObject:controllersToDeleteInCurrentSection]; 

    } 


    //copy the unfiltered data so we can remove the data that we want to filter out 
    NSMutableArray *newHeaders = [tableHeaders mutableCopy]; 
    NSMutableArray *newTableGroups = [[allTableGroups mutableCopy] autorelease]; 


    //removing controllers 
    int i = 0; 
    for(NSMutableArray *section in newTableGroups){ 
     NSIndexSet *indexesToDelete = [controllersToDelete objectAtIndex:i]; 
     [section removeObjectsAtIndexes:indexesToDelete]; 
     i++; 
    } 

    //removing empty sections and cooresponding headers 
    [newHeaders removeObjectsAtIndexes:sectionsToDelete]; 
    [newTableGroups removeObjectsAtIndexes:sectionsToDelete]; 

    //update headers 
    [tableHeaders release]; 
    tableHeaders = newHeaders; 

    //storing filtered table groups 
    self.filteredTableGroups = newTableGroups; 


    //filtering animation and presentation model update 
    [self.tableView beginUpdates]; 
    tableGroups = self.filteredTableGroups; 
    [self.tableView deleteSections:sectionsToDelete withRowAnimation:UITableViewRowAnimationTop]; 
    [self.tableView deleteRowsAtIndexPaths:cellsToDelete withRowAnimation:UITableViewRowAnimationTop]; 
    [self.tableView endUpdates]; 


    //marking table as filtered 
    self.tableIsFiltered = YES; 


} 

Je pense:

Le probl em semble être ceci: Si vous regardez ci-dessus où je liste le nombre de cellules dans chaque section, vous verrez que la section 5 semble augmenter de 1. Cependant, ce n'est pas vrai. La section 5 originale a été supprimée et une autre section a pris sa place (en particulier, il s'agit de l'ancienne section 10). Pourquoi la vue de la table semble-t-elle ne pas s'en rendre compte? Pourquoi? Il devrait savoir que j'ai supprimé l'ancienne section et ne devrait pas s'attendre à ce qu'une nouvelle section qui se trouve maintenant à l'index de l'ancienne section soit liée par le nombre de lignes de la section supprimée.

J'espère que cela a du sens, c'est un peu compliqué de l'écrire.

(Notez que ce code a déjà fonctionné avec un nombre différent de lignes/sections.cette configuration particulière semble donner des problèmes)

Répondre

87

J'ai rencontré ce problème avant. Vous essayez de supprimer toutes les lignes d'une section, puis, en plus, cette section maintenant vide. Cependant, il suffit (et approprié) de supprimer cette section seulement. Toutes les lignes qu'il contient seront également supprimées. Voici quelques exemples de code de mon projet qui gère la suppression d'une ligne. Il doit déterminer si elle doit supprimer uniquement cette ligne d'une section ou supprimer toute la section si elle est le dernier rang dans cette section:

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath 
{ 
    if (editingStyle == UITableViewCellEditingStyleDelete) 
    { 
     // modelForSection is a custom model object that holds items for this section. 
     [modelForSection removeItem:[self itemForRowAtIndexPath:indexPath]]; 

     [tableView beginUpdates]; 

     // Either delete some rows within a section (leaving at least one) or the entire section. 
     if ([modelForSection.items count] > 0) 
     { 
      // Section is not yet empty, so delete only the current row. 
      [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] 
          withRowAnimation:UITableViewRowAnimationFade]; 
     } 
     else 
     { 
      // Section is now completely empty, so delete the entire section. 
      [tableView deleteSections:[NSIndexSet indexSetWithIndex:indexPath.section] 
        withRowAnimation:UITableViewRowAnimationFade]; 
     } 

     [tableView endUpdates]; 
    } 
} 
4

Je remarque que vous supprimez d'abord les sections de la table, puis que vous supprimez des lignes.

Je sais qu'il existe un complicated discussion of batch insertion and deletion pour UITableViews dans le Guide de programmation de vue de table, mais il ne couvre pas spécifiquement cela.

Je pense que ce qui se passe, c'est que la suppression des sections provoque la suppression des lignes par rapport à la mauvaise ligne. C'est-à-dire que vous voulez supprimer la section 2 et la ligne 1 de la section 4 ... mais après avoir supprimé la section 2, l'ancienne section 4 est maintenant la troisième section, donc vous lorsque vous supprimez avec l'ancien NSIndexPath de (4, 1) vous supprimez une ligne aléatoire différent qui peut ne pas exister. Donc, je pense que le correctif peut être aussi simple que d'échanger ces deux lignes de code, donc vous supprimez les lignes d'abord, puis les sections.

+0

Vous pouvez également suivre l'indexPath pour chaque cellule dont vous devez vous débarrasser et les ajuster de manière appropriée au cours de vos suppressions. (Cela peut être la manière longue/alambiquée/inappropriée de le faire - juste une pensée.) – Tim

+0

Je fais une suppression par lot, donc cela ne fait aucune différence dans l'ordre dans lequel je liste les opérations. La vue de table effectue des opérations "à la fois" lorsqu'elles se trouvent dans le bloc de mise à jour. Comme j'étais paranoïaque, j'ai essayé de changer l'ordre des opérations en vain. La numérotation des sections/lignes ne change pas (ne devrait pas changer) pendant la suppression du lot. Si n'utilisait pas les blocs, vous auriez raison. –

+0

@Tim Pensée intéressante. Vous avez raison, cela pourrait être assez fastidieux avec une grande quantité de suppressions (que je vais avoir). Je me demande aussi si je pourrais faire des suppressions multiples en succession rapide. J'essayais de faire des suppressions de lots afin d'éviter ces problèmes, mais cela peut être nécessaire. –

3

Alors voici enfin ma solution à ce problème.

Comme précédemment, j'ai modifié le tableauview de Matt Gallagher qui place la logique spécifique à la cellule dans un contrôleur de cellule séparé. Cependant, vous pouvez facilement adapter cette méthode à un modèle différent

J'ai ajouté les éléments suivants (pertinents) Ivars au code de Matt:

NSArray *allTableGroups; //always has a copy of every cell controller, even if filtered 
NSArray *filteredTableGroups; //always has a copy of the filtered table groups 

Ivar original de Matt:

NSArray *allTableGroups 

... toujours des points à l'un des tableaux ci-dessus.

Cela peut probablement être refactorisé et amélioré de manière significative, mais je n'ai pas eu le besoin. De plus, si vous utilisez Core Data, NSFetchedResultsController rend cela plus facile.

Passons maintenant à la méthode (je suis en train de commenter autant que possible):

- (void)createFilteredTableGroups{ 

    //Checking for the usual suspects. all which may through an exception 
    if(model==nil) 
     return; 
    if(tableGroups==nil) 
     return; 
    if([tableGroups count]==0) 
     return; 


    //lets make a new array to work with 
    NSMutableArray *newTableGroups = [[allTableGroups mutableCopy] autorelease]; 

    //telling the table what we are about to do 
    [self.tableView beginUpdates]; 


    //array to track cells for deletion animation 
    NSMutableArray *indexesToRemove = [NSMutableArray array]; 

    //loop through each section 
    for(NSMutableArray *eachSection in tableGroups){ 

     //keeping track of the indexes to delete for each section 
     NSMutableIndexSet *indexesForSection = [NSMutableIndexSet indexSet]; 
     [indexesForSection removeAllIndexes]; 

     //increment though cell indexes 
     int rowIndex = 0; 

     //loop through each cellController in the section 
     for(ScheduleCellController *eachCellController in eachSection){ 

      //Ah ha! A little magic. the cell controller must know if it should be displayed. 
      //This you must calculate in your business logic 
      if(![eachCellController shouldDisplay]){ 

       //add non-displayed cell indexes 
       [indexesForSection addIndex:rowIndex]; 

      } 
      rowIndex++; 
     } 
     //adding each array of section indexes, EVEN if it is empty (no indexes to delete) 
     [indexesToRemove addObject:indexesForSection]; 

    } 

    //Now we remove cell controllers in newTableGroups and cells from the table 
    //Also, each subarray of newTableGroups is mutable as well 
    if([indexesToRemove count]>0){ 

     int sectionIndex = 0; 
     for(NSMutableIndexSet *eachSectionIndexes in indexesToRemove){ 

      //Now you know why we stuck the indexes into individual arrays, easy array method 
      [[newTableGroups objectAtIndex:sectionIndex] removeObjectsAtIndexes:eachSectionIndexes]; 

      //tracking which cell indexPaths to remove for each section 
      NSMutableArray *indexPathsToRemove = [NSMutableArray array]; 
      int numberOfIndexes = [eachSectionIndexes count]; 

      //create array of indexPaths to remove 
      NSUInteger index = [eachSectionIndexes firstIndex]; 
      for(int i = 0; i< numberOfIndexes; i++){ 

       NSIndexPath *indexPath = [NSIndexPath indexPathForRow:index inSection:sectionIndex]; 
       [indexPathsToRemove addObject:indexPath]; 
       index = [eachSectionIndexes indexGreaterThanIndex:index]; 
      } 

      //delete the rows for this section 
      [self.tableView deleteRowsAtIndexPaths:indexPathsToRemove withRowAnimation:UITableViewRowAnimationTop]; 

      //next section please 
      sectionIndex++; 
     } 

    } 

    //now we figure out if we need to remove any sections 
    NSMutableIndexSet *sectionsToRemove = [NSMutableIndexSet indexSet]; 
    [sectionsToRemove removeAllIndexes]; 

    int sectionsIndex = 0; 
    for(NSArray *eachSection in newTableGroups){ 

     //checking for empty sections 
     if([eachSection count]==0) 
      [sectionsToRemove addIndex:sectionsIndex]; 

     sectionsIndex++; 
    } 

    //updating the table groups 
    [newTableGroups removeObjectsAtIndexes:sectionsToRemove]; 

    //removing the empty sections 
    [self.tableView deleteSections:sectionsToRemove withRowAnimation:UITableViewRowAnimationTop]; 

    //updating filteredTableGroups to the newTableGroups we just created 
    self.filteredTableGroups = newTableGroups; 

    //pointing tableGroups at the filteredGroups 
    tableGroups = filteredTableGroups; 

    //invokes the animation 
    [self.tableView endUpdates]; 


} 
1

J'ai vu cette même erreur exactement à la suite de libérer prématurément la vue de fond de ma cellule tableview personnalisée. Avec NSZombieEnabled, une exception est levée en dessous d'un appel interne à une fonction pour préparer la cellule à être réutilisée. Sans NSZombieEnabled, je recevais l'erreur de cohérence interne. Incidemment, lorsque j'ai corrigé le problème de conservation/libération sur la vue d'arrière-plan de la cellule, j'ai pu supprimer la dernière ligne de la section sans avoir à supprimer explicitement la section.Moralité de l'histoire: Cette erreur signifie que quelque chose de mal se passe lorsque vous essayez de supprimer, et l'une des choses qui se passe lorsque vous supprimez est la cellule se prépare à la réutilisation, donc si vous faites quelque chose de personnalisé avec votre cellules de tableview, recherchez une erreur possible là-bas.

0

ou tout simplement faire ce

- (void)tableView:(UITableView *)tv  
commitEditingStyle:(UITableViewCellEditingStyle)editingStyle 
forRowAtIndexPath:(NSIndexPath *)indexPath { 

if(editingStyle == UITableViewCellEditingStyleDelete) {  
    //Delete the object from the table. 
    [directoriesOfFolder removeObjectAtIndex:indexPath.row]; 
    [tv deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] 
withRowAnimation:UITableViewRowAnimationFade]; 
} 
} 

répertoires de dossier étant votre tableau! C'est tout ce qui précède les codes n'a pas fonctionné pour moi! C'est moins cher à faire et a juste du sens!

2

Je suppose que vous oubliez de supprimer l'objet représentant la section de votre stockage interne, de sorte que la méthode -numberOfSectionsInTableView: renvoie toujours 1 après que toutes les sections ont été supprimées.

C'est exactement ce que je faisais mal quand j'ai eu le même crash!

1

Un moyen beaucoup plus simple pour résoudre c'est de mettre à jour votre source de données, puis appelez reloadSections

[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade]; 

Cela rechargera une seule section. Vous pouvez également utiliser indexSetWithIndexesInRange: pour recharger plusieurs sections simultanément.

Questions connexes