web-dev-qa-db-fra.com

UITableView: suppression de sections avec animation

Mettre à jour

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


Question originale J'ai déjà posé une question sur SO qui, à mon avis, aurait résolu mes problèmes:

Comment traiter les lignes non visibles lors de la suppression de lignes. (UITableViews)

Cependant, je rencontre maintenant des problèmes similaires lorsque je supprime des sections d'un UITableView . (Elles ont refait surface lorsque j'ai modifié le nombre de sections/lignes du tableau).

Avant que je ne vous perde à cause de la longueur de mon poste, laissez-moi exposer clairement le problème et vous pourrez lire autant que vous en aurez besoin pour donner une réponse.


Problème:

Si vous supprimez par lots des lignes ET des 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 indique que je me suis écrasé car il indique que je n'ai pas mis à jour correctement la source de données et le tableau:

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 j’ai bien ajouté et supprimé les lignes et les sections de la source de données. L'explication est longue, mais vous la trouverez ci-dessous, en suivant la méthode.

Alors avec ça, si vous êtes toujours intéressé…


Méthode permettant de supprimer 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 suppose:

Le problème semble être le suivant: 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 en fait été supprimée et une autre section l’a remplacée (c’est précisément la section 10).

Alors pourquoi la vue de la table semble ne pas s'en rendre compte? Sachez que j'ai supprimé l'ancienne section et ne devrait pas s'attendre à ce qu'une nouvelle section qui se trouve maintenant dans l'index de l'ancienne section soit liée au nombre de lignes de la section supprimée.

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

(notez que ce code fonctionnait auparavant avec un nombre différent de lignes/sections. Cette configuration particulière semble lui donner des problèmes) 

39
Corey Floyd

J'ai déjà rencontré ce problème auparavant. Vous essayez de supprimer toutes les lignes d'une section puis, en plus, cette section maintenant vide. Cependant, il est suffisant (et approprié) de ne supprimer que cette section. Toutes les lignes qu'il contient seront également supprimées. Voici un exemple de code de mon projet qui gère la suppression d’une ligne. Il doit déterminer s'il convient de supprimer uniquement cette ligne d'une section ou de supprimer la section entière s'il s'agit de la dernière ligne restante de 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];
    }
}
88
Martin Winter

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

Je sais qu'il existe une discussion compliquée sur l'insertion et la suppression de lots pour UITableViews dans le Guide de programmation sous forme de tableau, mais cela ne couvre pas spécifiquement cela. 

Je pense que ce qui se passe est que la suppression des sections fait que les suppressions de lignes se réfèrent à la mauvaise ligne.

c'est-à-dire que vous souhaitez supprimer la section n ° 2 et la ligne 1 de la section n ° 4 ... mais après avoir supprimé la section n ° 2, l'ancienne section n ° 4 est désormais la troisième section. (4, 1) vous supprimez une ligne aléatoire différente qui peut ne pas exister.

Je pense donc que la solution pourrait être aussi simple que d'échanger ces deux lignes de code. Vous supprimez donc les lignes en premier, puis les sections.

4
David Maymudes

Donc voici enfin ma solution à ce problème… .. Cette méthode peut être appliquée à des tables de toute taille, nombre de sections (autant que je sache)

Comme auparavant, j'ai modifié le code tableview de Matt Gallagher, qui place la logique spécifique à une cellule dans un contrôleur de cellule distinct. Cependant, vous pouvez facilement adapter cette méthode à un autre modèle 

J'ai ajouté les ivars suivants (pertinents) 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

L'ivar original de Matt:

NSArray *allTableGroups

… Pointe toujours vers l'un des tableaux ci-dessus.

Cela peut probablement être remanié et amélioré de manière significative, mais je n'en ai pas eu besoin. En outre, si vous utilisez des données de base, NSFetchedResultsController facilite cette opération.

Passons maintenant à la méthode (j'essaie de commenter autant que je peux):

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


}
3
Corey Floyd

Une façon beaucoup plus simple de résoudre ce problème consiste à mettre à jour votre source de données, puis appelez reloadSections

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

Cela rechargera une seule section. Sinon, vous pouvez utiliser indexSetWithIndexesInRange: pour recharger plusieurs sections simultanément.

1
Kyle Clegg

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

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

1
bfulgham

J'ai constaté cette même erreur exacte suite à la libération prématurée de la vue d'arrière-plan de ma cellule tableview personnalisée. 

Avec NSZombieEnabled, une exception a été rejetée en dessous d'un appel interne à une fonction afin de préparer la cellule en vue de sa réutilisation. Sans NSZombieEnabled, j'obtenais l'erreur de cohérence interne. 

Incidemment, lorsque j'ai résolu le problème de conservation/libération de la vue d'arrière-plan de la cellule, j'ai pu supprimer la dernière ligne de la section sans avoir à supprimer la section explicitement.

Morale de l'histoire: cette erreur signifie simplement que quelque chose de mauvais se produit lorsque vous essayez de supprimer, et l'une des choses qui se produit lorsque vous supprimez est que la cellule est prête à être réutilisée. Par conséquent, si vous effectuez une opération personnalisée avec vos cellules table, consultez pour une erreur possible là-bas.

1
TRK3

ou juste faire ceci

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

les répertoires du dossier étant votre tableau! Cest tout les codes ci-dessus ne fonctionnaient pas pour moi! C'est moins cher à faire et ça a du sens!

0
FreeAppl3