2009-06-29 14 views
37

aktualisierenUITableView: Löschen von Abschnitten mit Animation

Ich habe meine Lösung für dieses Problem als Antwort unten geschrieben. Es hat einen anderen Ansatz als meine erste Revision.


Original Question ich vorher eine Frage auf SO darum gebeten, dass ich dachte, meine Probleme gelöst:

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

Allerdings habe ich jetzt ähnlich wieder Probleme, wenn Abschnitte von einem UITableView zu entfernen. (sie tauchten wieder auf, wenn ich die Anzahl der Abschnitte/Reihen in der Tabelle änderte).

Bevor ich Sie wegen der Scherlänge von meinem Posten zu verlieren, lassen Sie mich das Problem deutlich machen, und man kann so viel lesen, wie Sie eine Antwort geben erfordern.


Problem:

Wenn Batch-Zeilen und Abschnitte aus einer UITableView löschen, die Anwendung abstürzt, manchmal. Dies hängt von der Konfiguration der Tabelle und der Kombination von Zeilen und Abschnitten ab, die ich entfernen möchte.

Das Protokoll sagt, ich abgestürzt, weil es sagt, ich habe nicht die Datenquelle und die Tabelle korrekt aktualisiert:

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). 

Jetzt schnell, bevor Sie die offensichtliche Antwort schreiben, ich versichern Ihnen, ich habe in die Tat hinzugefügt und gelöscht die Zeilen und Abschnitte ordnungsgemäß von der DataSource. Die Erklärung ist langwierig, aber Sie werden es unten finden und der Methode folgen.

Also mit diesem, wenn Sie noch interessiert ...


Methode, die Entfernung von Abschnitten und Zeilen Griffe:

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


} 

Meine Vermutung:

Das Problem em scheint das zu sein: Wenn Sie oben nachsehen, wo ich die Anzahl der Zellen in jedem Abschnitt aufliste, werden Sie sehen, dass Abschnitt 5 um 1 zu erhöhen scheint. Dies ist jedoch nicht wahr. Der ursprüngliche Abschnitt 5 wurde tatsächlich gelöscht und ein anderer Abschnitt hat seinen Platz eingenommen (speziell ist es der alte Abschnitt 10).

Warum scheint die Tabellenansicht dies nicht zu erkennen? Es sollte wissen, dass ich den alten Abschnitt und entfernt habe, sollte nicht erwarten, dass ein neuer Abschnitt, der jetzt im Index des alten Abschnitts liegt, an die Anzahl der Zeilen des gelöschten Abschnitts gebunden ist.

Hoffentlich macht Sinn, es ist ein wenig kompliziert ist, dies zu schreiben.

(beachten Sie, dass dieser Code zuvor mit einer anderen Anzahl von Zeilen/Abschnitten gearbeitet hat.Diese spezielle Konfiguration scheint es Probleme zu geben)

Antwort

87

Ich habe dieses Problem schon einmal erlebt. Sie versuchen, alle Zeilen aus einem Abschnitt und dann zusätzlich diesen jetzt leeren Abschnitt zu löschen. Es ist jedoch ausreichend (und angemessen), nur diesen Abschnitt zu entfernen. Alle Zeilen darin werden ebenfalls entfernt. Hier ist ein Beispielcode aus meinem Projekt, der das Löschen einer Zeile behandelt. Es muss festzustellen, ob es nur diese Zeile aus einem Abschnitt entfernen sollte oder den gesamten Abschnitt löscht, wenn er die letzte verbleibende Zeile in diesem Abschnitt ist:

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

Ich merke, dass Sie zuerst die Abschnitte aus der Tabelle löschen und dann Zeilen löschen.

Ich weiß, dass es eine complicated discussion of batch insertion and deletion für UITableViews in der Tabellenansicht Programmierhandbuch gibt, aber dies nicht speziell behandelt.

Ich denke, was passiert ist, dass das Löschen der Abschnitte bewirkt, dass die Zeilenlöschungen auf die falsche Zeile verweisen.

dh Sie möchten Abschnitt # 2 und Zeile # 1 aus Abschnitt # 4 löschen ... aber nachdem Sie Abschnitt # 2 gelöscht haben, ist der alte Abschnitt # 4 jetzt der dritte Abschnitt, also Sie beim Löschen mit Der alte NSIndexPath von (4, 1) Sie löschen eine zufällige andere Zeile, die möglicherweise nicht existiert.

Also ich denke, die Fehlerbehebung könnte so einfach wie das Austauschen dieser beiden Zeilen des Codes sein, so dass Sie zuerst die Zeilen löschen, dann die Abschnitte.

+0

Verfolgen Sie alternativ den IndexPath für jede Zelle, die Sie loswerden müssen, und passen Sie sie entsprechend an, wenn Sie Ihre Löschungen durchlaufen. (Dies könnte die lange/gewundene/unangemessene Art sein, dies zu tun - nur ein Gedanke.) – Tim

+0

Ich mache eine Batch-Löschung, so dass es keinen Unterschied macht, in welcher Reihenfolge ich die Operationen listet. Die Tabellenansicht führt Operationen "sofort" aus, wenn sie sich innerhalb des Aktualisierungsblocks befinden. Da ich paranoid war, habe ich versucht, die Reihenfolge der Operationen umsonst zu ändern. Die Nummerierung der Abschnitte/Zeilen wechselt während des Batch-Löschens nicht (sollte nicht wechseln). Wenn Sie die Blöcke nicht benutzen würden, wären Sie richtig. –

+0

@Tim Interessanter Gedanke. Du hast Recht, das könnte ziemlich mühsam sein mit einer großen Anzahl von Löschungen (die ich haben werde). Ich frage mich auch, ob ich mehrere Streichungen in schneller Folge machen könnte. Ich habe versucht, Batch-Löschungen durchzuführen, um diese Probleme zu vermeiden, aber es kann notwendig sein. –

3

So endlich hier ist meine Lösung für dieses Problem. Diese Methode kann auf Tabellen beliebiger Größe angewendet werden, beliebig viele Abschnitte (soweit ich das beurteilen kann)

Wie zuvor habe ich Matt Gallaghers Tabellenansicht Code geändert, die zellspezifische Logik in einem separaten Zellencontroller platziert. Sie können jedoch leicht, diese Methode zu einem anderen Modell anpassen

ich hinzugefügt haben, die folgenden (relevanten) ivars Matts Code:

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

Matts Original Ivar:

NSArray *allTableGroups 

... immer Punkte zu einem der obigen Arrays.

Dies kann wahrscheinlich umgestaltet und erheblich verbessert werden, aber ich hatte nicht die Notwendigkeit. Wenn Sie Core Data verwenden, erleichtert NSFetchedResultsController dies außerdem.

nun auf das Verfahren (Ich versuche, so viel wie ich kann, um einen Kommentar):

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

habe ich diesen gleichen genauen Fehler als Ergebnis der vorzeitigen Freigabe der Hintergrundansicht von meiner benutzerdefinierten Tableview Zelle.

Mit NSZombieEnabled habe ich eine Ausnahme erhalten, die weit unter einen internen Aufruf einer Funktion geworfen wurde, um die Zelle für die Wiederverwendung vorzubereiten. Ohne NSZombieEnabled habe ich den Fehler "Interne Konsistenz" erhalten.

Übrigens, als ich das Behalten/Lösen Problem in der Hintergrundansicht der Zelle behoben habe, konnte ich die letzte Zeile des Abschnitts löschen, ohne den Abschnitt explizit löschen zu müssen.

Moral der Geschichte: Dieser Fehler bedeutet nur etwas Schlimmes passiert, wenn Sie versuchen, zu löschen, und eine der Dinge, die passiert, wenn Sie löschen, ist die Zelle wird für die Wiederverwendung vorbereitet, also wenn Sie irgendetwas mit Ihrem Brauch tun Tabellenansichtszellen, suchen Sie dort nach einem möglichen Fehler.

0

oder einfach nur tun dies

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

Verzeichnisse von Ordnern Ihr Array zu sein! Das ist alles über Codes funktionierte nicht für mich! Dies ist weniger teuer und macht nur Sinn!

2

Ich vermute, dass Sie vergessen, das Objekt, das den Abschnitt darstellt, aus Ihrem internen Speicher zu entfernen, sodass die Methode -numberOfSectionsInTableView: immer noch 1 zurückgibt, nachdem alle Abschnitte gelöscht wurden.

Genau das habe ich falsch gemacht, als ich den gleichen Crash hatte!

1

Ein viel einfacher Weg, dies zu adressieren ist Ihre Datenquelle zu aktualisieren, rufen Sie dann reloadSections

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

Dies wird einen einzigen Abschnitt neu zu laden. Alternativ können Sie indexSetWithIndexesInRange: verwenden, um mehrere Abschnitte gleichzeitig neu zu laden.

Verwandte Themen