2009-06-29 13 views
37

actualizaciónUITableView: la supresión de secciones con animación

He publicado mi solución a este problema como una respuesta a continuación. Toma un enfoque diferente de mi primera revisión.


pregunta original anteriormente he hecho una pregunta sobre el SO que pensé resuelto mis problemas:

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

Sin embargo, ahora tienen problemas similares de nuevo cuando la eliminación de secciones de un UITableView. (resurgieron cuando cambié el número de secciones/filas en la tabla).

Antes de perderlo debido a la longitud de corte de mi publicación, permítame exponer el problema con claridad, y puede leer todo lo que necesite para dar una respuesta.


Problema:

Si lote eliminar filas y secciones de un UITableView, la aplicación se bloquea, a veces. Depende de la configuración de la tabla y la combinación de filas y secciones que elijo eliminar.

El registro dice que me he caído porque dice que no he actualizado el origen de datos y la tabla correctamente:

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

Ahora con rapidez, antes de escribir la respuesta obvia, les aseguro que he añadido de hecho y eliminado el filas y secciones correctamente desde dataSource. La explicación es larga, pero la encontrará más abajo, siguiendo el método.

Así que con eso, si usted todavía está interesado ...


método que controla la eliminación de secciones y filas:

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


} 

Mi suposición:

El probl em parece ser este: si miras arriba donde enumero la cantidad de celdas en cada sección, verás que la sección 5 parece aumentar en 1. Sin embargo, esto no es cierto. La sección original 5 se ha eliminado y otra sección ha ocupado su lugar (específicamente, es la antigua sección 10).

¿Por qué la vista de tabla parece no darse cuenta de esto? Debe SABER que eliminé la sección anterior y no debería esperar que una nueva sección que ahora se encuentra en el índice de la sección anterior se vincule con el número de filas de la sección eliminada.

Esperemos que esto tenga sentido, es un poco complicado escribir esto.

(tenga en cuenta que este código funcionaba antes con un número diferente de filas/secciones.esta configuración particular parece darle problemas)

Respuesta

87

Me he encontrado con este problema antes. Está intentando eliminar todas las filas de una sección y luego, además, esa sección ahora vacía. Sin embargo, es suficiente (y apropiado) eliminar solo esa sección. Todas las filas dentro de ella serán eliminadas también. Aquí hay un código de muestra de mi proyecto que maneja la eliminación de una fila. Se necesita determinar si debe eliminar únicamente esta fila de una sección o eliminar toda la sección si es la última fila restante en esa sección:

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

Noté que primero está eliminando las secciones de la tabla y luego elimina las filas.

Yo sé que hay una complicated discussion of batch insertion and deletion para UITableViews en la Tabla Guía de programación, pero no cubre específicamente.

Creo que lo que está sucediendo es que borrar las secciones hace que las eliminaciones de filas hagan referencia a la fila incorrecta.

es decir, desea eliminar la sección n. ° 2 y la fila n. ° 1 de la sección n. ° 4 ... pero después de eliminar la sección n. ° 2, la sección n. ° 4 es ahora la tercera, de modo el antiguo NSIndexPath de (4, 1) está borrando una fila aleatoria diferente que puede no existir.

Creo que la solución podría ser tan simple como intercambiar esas dos líneas de código, por lo que primero se eliminan las filas y luego las secciones.

+0

De forma alternativa, realice un seguimiento de indexPath para cada celda que necesita eliminar, y ajústelas adecuadamente a medida que vaya borrando sus datos. (Esta podría ser la forma larga/intrincada/inapropiada de hacerlo, solo un pensamiento.) – Tim

+0

Estoy haciendo una eliminación por lotes, por lo que no hace ninguna diferencia en el orden de las operaciones de la lista. La vista de tabla realiza operaciones "a la vez" cuando están dentro del bloque de actualización. Como estaba paranoico, intenté cambiar el orden de las operaciones en vano. La numeración de las secciones/filas no cambia (no debe cambiar) durante la eliminación del lote. Si no estuvieras usando los bloques, estarías en lo correcto. –

+0

@Tim Interesante pensamiento. Tienes razón, eso podría ser bastante tedioso con una gran cantidad de eliminaciones (que tendré). También me pregunto si podría hacer varias eliminaciones en rápida sucesión. Estaba tratando de eliminar lotes para evitar estos problemas, pero puede ser necesario. –

3

Así que, finalmente, aquí está mi solución a este problema. Este método se puede aplicar a tablas de cualquier tamaño, cualquier número de secciones (por lo que puedo decir)

Como antes he modificado el código de tabla de Matt Gallagher que coloca la lógica específica de celda en un controlador de celda separado. Sin embargo, se puede adaptar fácilmente este método a un modelo diferente

He añadido los Ivars siguientes (relevantes) con el código 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 originales de Matt:

NSArray *allTableGroups 

... siempre puntos a una de las matrices anteriores.

Esto probablemente se puede refactorizar y mejorar significativamente, pero no he tenido la necesidad. Además, si utiliza Core Data, NSFetchedResultsController lo hace más fácil.

Ahora vamos con el método (que estoy tratando de hacer comentarios tanto como pueda):

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

vi este mismo error exacto como el resultado de la liberación prematura de la opinión del fondo de mi celda tableview personalizado.

Con NSZombieEnabled recibí una excepción lanzada muy por debajo de una llamada interna a una función para preparar la célula para su reutilización. Sin NSZombieEnabled, obtenía el error de coherencia interno.

A propósito, cuando solucioné el problema de retención/liberación en la vista de fondo de la celda, pude eliminar la última fila de la sección sin tener que eliminar la sección explícitamente.

Moraleja de la historia: Este error solo significa que algo malo está sucediendo cuando intentas eliminar, y una de las cosas que sucede cuando eliminas es que la célula se prepara para ser reutilizada, así que si estás haciendo algo personalizado con tu celdas tableview, busque un posible error allí.

0

o simplemente hacer esto

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

directorios de la carpeta ser su matriz! ¡Eso es todo lo anterior, los códigos no me funcionaron! ¡Esto es menos costoso de hacer y simplemente tiene sentido!

2

Sospecho que se olvida de eliminar el objeto que representa la sección de su almacenamiento interno, por lo que el método -numberOfSectionsInTableView: sigue devolviendo 1 después de que se hayan eliminado todas las secciones.

¡Eso es exactamente lo que estaba haciendo mal cuando tuve el mismo choque!

1

Una manera mucho más simple de abordar esto es para actualizar el origen de datos, a continuación, llamar reloadSections

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

Esto provocará que se recargue una sola sección. Alternativamente, puede usar indexSetWithIndexesInRange: para volver a cargar varias secciones simultáneamente.

Cuestiones relacionadas