8

Después de días de investigación y re-codificación estoy bastante perplejo. Mi objetivo es hacer que se ejecute una aplicación de prueba con una única tabla vista poblada de dos fetchedResultControllers separados.TableView con dos instancias de NSFetchedResultsController

Tengo una serie de artículos en una lista de compras, cada uno con un departamento y una bandera booleana 'recogida'. Los artículos no recogidos deben enumerarse por departamento, seguidos de una sola sección que contiene todos los artículos recolectados (independientemente del departamento). A medida que un usuario marque los elementos no cobrados, se moverá a la sección "recolectada". Si él/ella elimina la marca de un artículo recolectado, debe regresar a su departamento correcto.

enter image description here

Para lograr la primera parte (artículos no cobrados), puedo tener un fetchedResultsController sencilla que recupera todos los elementos en los que recogen = NO, y se seccionaron los resultados por departamento:

- (NSFetchedResultsController *)firstFRC { 
    // Set up the fetched results controller if needed. 
    if (firstFRC == nil) { 

     // Create the fetch request for the entity. 
     NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; 

     // fetch items 
     NSEntityDescription *entity = [NSEntityDescription entityForName:@"Item" inManagedObjectContext:managedObjectContext]; 
     [fetchRequest setEntity:entity]; 

     // only if uncollected 
     NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(collected = NO)"]; 
     [fetchRequest setPredicate:predicate]; 

     // sort by name 
     NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES]; 
     NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil]; 

     [fetchRequest setSortDescriptors:sortDescriptors]; 

     // fetch results, sectioned by department 
     NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext sectionNameKeyPath:@"department" cacheName:nil]; 
     aFetchedResultsController.delegate = self; 
     self.firstFRC = aFetchedResultsController; 
    } 

    return firstFRC; 
} 

yo pongo el número de filas, secciones y encabezados de sección como sigue:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 
{ 
    // Return the number of sections. 
    return firstFRC.sections.count; 
} 

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 
{ 
    // Return the number of rows in the section. 
    return [[firstFRC.sections objectAtIndex:section] numberOfObjects]; 
} 

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { 
    return [[[firstFRC sections] objectAtIndex:section] name]; 
} 

también utilizo el controllerWillChangeContent repetitivo, didCha ngeObject y controllerDidChangeContent para agregar/eliminar celdas a medida que cambian los FRC.

Por razones de brevedad, no incluirá el código para la visualización de la célula, pero esencialmente tirar el producto correcto basado en ruta del índice de la célula, establecer el texto/subtítulo de la célula, y adjuntar una de las dos imágenes de marca de verificación, dependiendo de si el artículo es recolectado o no. También conecto esta imagen (que está en un botón) para alternar entre marcada y no marcada cuando se toca, y actualizo el ítem en consecuencia.

Esta parte funciona bien. Puedo ver mi lista de artículos por departamento, y cuando marco uno como recogido, lo veo abandonar la lista como se esperaba.

Ahora intenté agregar la sección inferior, que contiene todos los elementos recopilados (en una sola sección). Primero configuré un segundo fetchedResultsConroller, esta vez para buscar solo artículos no recogidos y sin seccionar. También tuve que actualizar la siguiente:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 
{ 
    // Return the number of sections - add one for 'collected' section 
    return firstFRC.sections.count + 1; 
} 

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 
{ 
    // Return the number of rows in the section 
    if (section < firstFRC.sections.count) { 
     return [[firstFRC.sections objectAtIndex:section] numberOfObjects]; 
    } 
    else { 
     return secondFRC.fetchedObjects.count; 
    } 
} 

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { 
    if (section < firstFRC.sections.count) { 
     return [[[firstFRC sections] objectAtIndex:section] name]; 
    } 
    else{ 
     return @"Collected"; 
    } 
} 

entonces he actualizado el cellForRowAtIndexPath de una manera similar, de modo que el elemento recupero proviene de la FRC derecha:

Item *item; 
    if (indexPath.section < firstFRC.sections.count) { 
     item = [firstFRC objectAtIndexPath:indexPath]; 
    } 
    else { 
     item = [secondFRC objectAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row inSection:0]]; 
    } 

    [[cell textLabel] setText:[item name]]; 

…rest of cell configuration 

Esto funciona muy bien cuando inicio . Los Tableview muestra exactamente como se había previsto:

enter image description here

El problema (por fin)

La (primera) problema viene cuando selecciono la marca de verificación de un elemento no cobrados. Espero que el artículo se elimine del departamento en el que figura, y se mueva a la sección "recopilada".En su lugar, me sale:

CoreData: error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. Invalid update: invalid number of rows in section 0. 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 (2), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out). with userInfo (null)

Si intento lo contrario, entonces recibo un error diferente:

* Terminating app due to uncaught exception 'NSRangeException', reason: '* -[__NSArrayM objectAtIndex:]: index 2 beyond bounds [0 .. 1]'

Sospecho que en ambos casos hay un problema con la consistencia con el número de secciones/filas en los FRC y la vista de tabla cuando un elemento se mueve de un FRC a otro. Aunque ese segundo error me hace pensar que tal vez haya un problema más simple relacionado con mi recuperación de artículos.

Cualquier dirección o idea sería apreciada. Puedo proporcionar más de mi código si me ayudaría, y también he creado una pequeña aplicación de prueba con una sola vista para demostrar el problema. Puedo subirlo si es necesario, pero sobre todo quería probar el problema en una caja de arena de pequeña escala.

Actualización - código adicional solicitada

a lo solicitado, esto es lo que sucede cuando se ha marcado tocó:

- (void)checkButtonTapped:(id)sender event:(id)event 
{ 
    NSSet *touches = [event allTouches]; 
    UITouch *touch = [touches anyObject]; 
    CGPoint currentTouchPosition = [touch locationInView:self.tableView]; 
    NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint: currentTouchPosition]; 
    if (indexPath != nil) 
    { 
     [self tableView: self.tableView accessoryButtonTappedForRowWithIndexPath: indexPath]; 
    } 
} 

- (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath 
{ 
    Item *item; 
    if (indexPath.section < firstFRC.sections.count) { 
     item = [firstFRC objectAtIndexPath:indexPath]; 
    } 
    else { 
     item = [secondFRC objectAtIndexPath:[NSIndexPath indexPathForRow:indexPath.row inSection:0]]; 
    } 

    UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath]; 
    UIButton *button = (UIButton *)cell.accessoryView; 

    if (![item collected]) { 
     [item setCollected:YES];   
     [button setBackgroundImage:[UIImage imageNamed:@"checked.png"] forState:UIControlStateNormal]; 
    } 
    else if ([item collected]){ 
     [item setCollected:NO]; 
     [button setBackgroundImage:[UIImage imageNamed:@"unchecked.png"] forState:UIControlStateNormal]; 
    } 
    NSError *error = nil; 
    if (![item.managedObjectContext save:&error]) { 
     NSLog(@"Error saving collected items"); 
    } 
} 
+0

Nota: los nombres de los departamentos están ligeramente apagados en la captura de pantalla. Olvidé usar el departamento como el primer descriptor de clasificación. –

+0

¿Cómo está recargando su mesa? –

+0

Publica el código que se llama cuando se toca una marca de verificación. – jcm

Respuesta

6

Bueno, creo que podría haberlo descifrado. Como suele ser el caso, desnudarlo y leer solo unos pocos comentarios me indicaron la dirección correcta.

Cuando llego al método de delegado controllerDidChangeObject, intento insertar una fila en el indexPath proporcionado (para el elemento 'checked'). Excepto que al insertar en mi sección adicional, este indexPath no tiene conciencia del hecho de que hay muchas otras secciones antes. Entonces recibe la sección 0 e intenta insertar allí. En cambio, si indexPath proviene del segundo FRC, debería incrementar el número de sección por el número de secciones en la primera tabla de FRC. Por lo tanto, he sustituido:

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath { 

UITableView *tableView = self.tableView; 

    switch(type) { 
     case NSFetchedResultsChangeInsert: 
       [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; 

     break; 

con

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath { 

UITableView *tableView = self.tableView; 

switch(type) { 
    case NSFetchedResultsChangeInsert: 

     if ([controller.sectionNameKeyPath isEqualToString:@"department"]) { 
      [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; 
     } 
     else { 
      [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:newIndexPath.row inSection:firstFRC.sections.count]] withRowAnimation:UITableViewRowAnimationFade];   } 
     break; 

voy a tener que hacer esto para cada uno de insertar, eliminar, actualizar, etc. Voy a marcar esto como la respuesta después de haber validado esto y para dé tiempo para otros comentarios.

+0

Se confirmó que funcionaba después de este cambio. –

+0

También puede verificar si ([controller isEqualTo: self.firstFetchedResultsController]) – 3lvis

0

Los errores que está viendo significa que sus UITableView recargas sí mismo antes de que ambos su NSFetchedResultsController s hacer. Los códigos que publicaste son probablemente correctos. Sospecho que el problema está en uno de los métodos NSFetchedResultsControllerDelegate.

+0

¿Quiere decir controllerWillChangeContent, etc.? Estos arranqué al por mayor de la demostración de Apple. –

+0

Usted sospechó correctamente, muchas gracias! –

Cuestiones relacionadas