usar tutorial porque persistencia efecto datos data apple iphone uitableview core-data

iphone - porque - swift 4 core data tutorial



Reorganización de UITableView con Core Data (8)

Posible duplicado:
¿Cómo implementar el reordenamiento de los registros de CoreData?

Estoy tratando de encontrar un ejemplo de código que muestre cómo manejar mover / reorganizar las celdas en un tableView cuando la celda usa un control de resultados fetched (es decir, junto con los datos centrales). Obtengo moveRowAtIndexPath: llame a mi fuente de datos, pero no puedo encontrar la combinación correcta de magia negra para que la tabla / datos reconozcan el cambio correctamente.

Por ejemplo, cuando muevo la fila 0 a la fila 2 y luego la dejo ir, se ve "correcta". Luego hago clic en "Hecho". La fila (1) que se había deslizado hasta completar la fila 0 todavía tiene su aspecto de modo de edición (menos y mover iconos), mientras que las otras filas a continuación vuelven a su aspecto normal. Si me desplazo hacia abajo, como la fila 2 (originalmente 0, ¿recuerdas?) Se acerca a la parte superior, desaparece por completo.

WTF. ¿Debo invalidar de alguna manera el fetchResultsController? Cada vez que lo configuro a cero, tengo bloqueos. ¿Debo liberarlo en su lugar? ¿Estoy en la maleza?

Esto es lo que tengo actualmente allí ...

- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath { NSManagedObjectContext *context = [fetchedResultsController managedObjectContext]; /* Update the links data in response to the move. Update the display order indexes within the range of the move. */ if (fromIndexPath.section == toIndexPath.section) { NSInteger start = fromIndexPath.row; NSInteger end = toIndexPath.row; NSInteger i = 0; if (toIndexPath.row < start) start = toIndexPath.row; if (fromIndexPath.row > end) end = fromIndexPath.row; for (i = start; i <= end; i++) { NSIndexPath *tempPath = [NSIndexPath indexPathForRow:i inSection:toIndexPath.section]; LinkObj *link = [fetchedResultsController objectAtIndexPath:tempPath]; //[managedObjectContext deleteObject:[fetchedResultsController objectAtIndexPath:tempPath]]; link.order = [NSNumber numberWithInteger:i]; [managedObjectContext refreshObject:link mergeChanges:YES]; //[managedObjectContext insertObject:link]; } } // Save the context. NSError *error; if (![context save:&error]) { // Handle the error... } } - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller { // The fetch controller is about to start sending change notifications, so prepare the table view for updates. if (self.theTableView != nil) [self.theTableView beginUpdates]; } - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { // The fetch controller has sent all current change notifications, so tell the table view to process all updates. if (self.theTableView != nil) { [self.theTableView endUpdates]; } }


Cuando mueve una fila en la vista de tabla, realmente mueve un bloque de otras filas (que consta de al menos una fila) en la otra dirección al mismo tiempo. El truco es actualizar solo la propiedad displayOrder de este bloque y del elemento movido.

Primero, asegúrese de que la propiedad displayOrder de todas las filas esté establecida de acuerdo con el orden de visualización actual de las tablas. No tenemos que guardar el contexto aquí, lo guardaremos más adelante cuando finalice la operación de movimiento real:

- (void)setEditing:(BOOL)editing animated:(BOOL)animated { [super setEditing:editing animated:animated]; [_tableView setEditing:editing animated:animated]; if(editing) { NSInteger rowsInSection = [self tableView:_tableView numberOfRowsInSection:0]; // Update the position of all items for (NSInteger i=0; i<rowsInSection; i++) { NSIndexPath *curIndexPath = [NSIndexPath indexPathForRow:i inSection:0]; SomeManagedObject *curObj = [_fetchedResultsController objectAtIndexPath:curIndexPath]; NSNumber *newPosition = [NSNumber numberWithInteger:i]; if (![curObj.displayOrder isEqualToNumber:newPosition]) { curObj.displayOrder = newPosition; } } } }

Entonces, lo único que tiene que hacer es actualizar la posición del elemento movido y de todos los elementos entre fromIndexPath y toIndexPath:

- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath { NSInteger moveDirection = 1; NSIndexPath *lowerIndexPath = toIndexPath; NSIndexPath *higherIndexPath = fromIndexPath; if (fromIndexPath.row < toIndexPath.row) { // Move items one position upwards moveDirection = -1; lowerIndexPath = fromIndexPath; higherIndexPath = toIndexPath; } // Move all items between fromIndexPath and toIndexPath upwards or downwards by one position for (NSInteger i=lowerIndexPath.row; i<=higherIndexPath.row; i++) { NSIndexPath *curIndexPath = [NSIndexPath indexPathForRow:i inSection:fromIndexPath.section]; SomeManagedObject *curObj = [_fetchedResultsController objectAtIndexPath:curIndexPath]; NSNumber *newPosition = [NSNumber numberWithInteger:i+moveDirection]; curObj.displayOrder = newPosition; } SomeManagedObject *movedObj = [_fetchedResultsController objectAtIndexPath:fromIndexPath]; movedObj.displayOrder = [NSNumber numberWithInteger:toIndexPath.row]; NSError *error; if (![_fetchedResultsController.managedObjectContext save:&error]) { NSLog(@"Could not save context: %@", error); } }



Esto es lo que oficialmente funciona ahora, con eliminaciones, movimientos e inserciones. Yo "valido" la orden cada vez que hay una acción de edición que afecta el orden.

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.section != kHeaderSection) { if (editingStyle == UITableViewCellEditingStyleDelete) { @try { LinkObj * link = [self.fetchedResultsController objectAtIndexPath:indexPath]; debug_NSLog(@"Deleting at indexPath %@", [indexPath description]); //debug_NSLog(@"Deleting object %@", [link description]); if ([self numberOfBodyLinks] > 1) [self.managedObjectContext deleteObject:link]; } @catch (NSException * e) { debug_NSLog(@"Failure in commitEditingStyle, name=%@ reason=%@", e.name, e.reason); } } else if (editingStyle == UITableViewCellEditingStyleInsert) { // we need this for when they click the "+" icon; just select the row [theTableView.delegate tableView:tableView didSelectRowAtIndexPath:indexPath]; } } } - (BOOL)validateLinkOrders { NSUInteger index = 0; @try { NSArray * fetchedObjects = [self.fetchedResultsController fetchedObjects]; if (fetchedObjects == nil) return NO; LinkObj * link = nil; for (link in fetchedObjects) { if (link.section.intValue == kBodySection) { if (link.order.intValue != index) { debug_NSLog(@"Info: Order out of sync, order=%@ expected=%d", link.order, index); link.order = [NSNumber numberWithInt:index]; } index++; } } } @catch (NSException * e) { debug_NSLog(@"Failure in validateLinkOrders, name=%@ reason=%@", e.name, e.reason); } return (index > 0 ? YES : NO); } - (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath { NSArray * fetchedObjects = [self.fetchedResultsController fetchedObjects]; if (fetchedObjects == nil) return; NSUInteger fromRow = fromIndexPath.row + NUM_HEADER_SECTION_ROWS; NSUInteger toRow = toIndexPath.row + NUM_HEADER_SECTION_ROWS; NSInteger start = fromRow; NSInteger end = toRow; NSInteger i = 0; LinkObj *link = nil; if (toRow < start) start = toRow; if (fromRow > end) end = fromRow; @try { for (i = start; i <= end; i++) { link = [fetchedObjects objectAtIndex:i]; // //debug_NSLog(@"Before: %@", link); if (i == fromRow) // it''s our initial cell, just set it to our final destination link.order = [NSNumber numberWithInt:(toRow-NUM_HEADER_SECTION_ROWS)]; else if (fromRow < toRow) link.order = [NSNumber numberWithInt:(i-1-NUM_HEADER_SECTION_ROWS)]; // it moved forward, shift back else // if (fromIndexPath.row > toIndexPath.row) link.order = [NSNumber numberWithInt:(i+1-NUM_HEADER_SECTION_ROWS)]; // it moved backward, shift forward //debug_NSLog(@"After: %@", link); } } @catch (NSException * e) { debug_NSLog(@"Failure in moveRowAtIndexPath, name=%@ reason=%@", e.name, e.reason); } } - (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath { @try { switch (type) { case NSFetchedResultsChangeInsert: [theTableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; [self validateLinkOrders]; break; case NSFetchedResultsChangeUpdate: break; case NSFetchedResultsChangeMove: self.moving = YES; [self validateLinkOrders]; break; case NSFetchedResultsChangeDelete: [theTableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; [self validateLinkOrders]; break; default: break; } } @catch (NSException * e) { debug_NSLog(@"Failure in didChangeObject, name=%@ reason=%@", e.name, e.reason); } } - (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type { switch(type) { case NSFetchedResultsChangeInsert: [self.theTableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade]; break; case NSFetchedResultsChangeDelete: [self.theTableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade]; break; } } - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { // The fetch controller has sent all current change notifications, so tell the table view to process all updates. @try { if (self.theTableView != nil) { //[self.theTableView endUpdates]; if (self.moving) { self.moving = NO; [self.theTableView reloadData]; //[self performSelector:@selector(reloadData) withObject:nil afterDelay:0.02]; } [self performSelector:@selector(save) withObject:nil afterDelay:0.02]; } } @catch (NSException * e) { debug_NSLog(@"Failure in controllerDidChangeContent, name=%@ reason=%@", e.name, e.reason); } }


La mejor respuesta es en realidad en el comentario de Clint Harris sobre la pregunta:

cimgf.com/2010/06/05/re-ordering-nsfetchedresultscontroller

Para resumir rápidamente, la parte esencial es tener una propiedad displayOrder en los objetos que está intentando reordenar con la descripción de clasificación para el orden de control de resultados obtenidos en ese campo. El código para moveRowAtIndexPath:toIndexPath: luego se ve así:

- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath; { NSMutableArray *things = [[fetchedResultsController fetchedObjects] mutableCopy]; // Grab the item we''re moving. NSManagedObject *thing = [[self fetchedResultsController] objectAtIndexPath:sourceIndexPath]; // Remove the object we''re moving from the array. [things removeObject:thing]; // Now re-insert it at the destination. [things insertObject:thing atIndex:[destinationIndexPath row]]; // All of the objects are now in their correct order. Update each // object''s displayOrder field by iterating through the array. int i = 0; for (NSManagedObject *mo in things) { [mo setValue:[NSNumber numberWithInt:i++] forKey:@"displayOrder"]; } [things release], things = nil; [managedObjectContext save:nil]; }

La documentación de Apple también contiene consejos importantes:

https://developer.apple.com/library/ios/#documentation/CoreData/Reference/NSFetchedResultsControllerDelegate_Protocol/Reference/Reference.html

Esto también se menciona en Cómo implementar el reordenamiento de registros CoreData?

Para citar la documentación de Apple:

Actualizaciones dirigidas por el usuario

En general, NSFetchedResultsController está diseñado para responder a los cambios en la capa del modelo. Si permite que un usuario vuelva a ordenar las filas de la tabla, su implementación de los métodos de delegado debe tener esto en cuenta.

Normalmente, si permite al usuario reordenar las filas de la tabla, su objeto modelo tiene un atributo que especifica su índice. Cuando el usuario mueve una fila, actualiza este atributo en consecuencia. Esto, sin embargo, tiene el efecto secundario de hacer que el controlador note el cambio e informar a su delegado de la actualización (usando el controlador: didChangeObject: atIndexPath: forChangeType: newIndexPath :). Si simplemente usa la implementación de este método que se muestra en "Uso típico", entonces el delegado intenta actualizar la vista de tabla. La vista de tabla, sin embargo, ya está en el estado apropiado debido a la acción del usuario.

En general, por lo tanto, si admite actualizaciones dirigidas por el usuario, debe establecer un indicador si el usuario inicia un movimiento. En la implementación de sus métodos de delegado, si se establece el indicador, omitirá las implementaciones del método principal; por ejemplo:

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath { if (!changeIsUserDriven) { UITableView *tableView = self.tableView; // Implementation continues...


Lo siento Greg, estoy seguro de que estoy haciendo algo mal, pero tu respuesta no funciona para mí.

Aunque todos mis objetos se validan correctamente, cuando salgo del modo de edición, una de las filas se congela (los controles de edición no desaparecen) y las células no responden correctamente después.

Tal vez mi problema es que no sé cómo usar la propiedad de movimiento que estableces (self.moving = YES). ¿Podría aclarar esto? Muchas gracias.

Jorge


Por lo general, cuando ve artefactos así, lo que está sucediendo es que la interfaz de usuario se ha animado a una nueva posición y se lo contó, entonces las actualizaciones que ha hecho a su modelo no reflejan correctamente el estado que da como resultado fallas la próxima vez que view tiene que referirse al modelo para una actualización.

Creo que no entiendes exactamente lo que se supone que debes hacer en el método. Se llama porque la interfaz de usuario ha cambiado y es necesario que el modelo cambie en consecuencia. El siguiente código supone que los resultados ya están en el nuevo orden y solo necesita reiniciar el campo de pedido por alguna razón:

for (i = start; i <= end; i++) { NSIndexPath *tempPath = [NSIndexPath indexPathForRow:i inSection:toIndexPath.section]; LinkObj *link = [fetchedResultsController objectAtIndexPath:tempPath]; //[managedObjectContext deleteObject:[fetchedResultsController objectAtIndexPath:tempPath]]; link.order = [NSNumber numberWithInteger:i]; [managedObjectContext refreshObject:link mergeChanges:YES]; //[managedObjectContext insertObject:link]; }

El problema es que en realidad no está cambiando el orden en el modelo subyacente. Esos indexPaths son de UITableViewController, te dice que el usuario arrastró esos a spots y necesitas actualizar los datos subyacentes de acuerdo. Pero fetchedResultsController siempre está en orden de clasificación, por lo tanto, hasta que haya cambiado esas propiedades, nada se ha movido.

Lo que ocurre es que no se han movido, se te llama para decirte que tienes que moverlos (ajustando la propiedad ordenable). Realmente necesitas algo más como:

NSNumber *targetOrder = [fetchedResultsController objectAtIndexPath:toIndexPath]; LinkObj *link = [fetchedResultsController objectAtIndexPath:FromPath]; link.order = targetOrder;

Esto hará que los objetos se reordenen, luego revisen y limpien cualquiera de los números de orden de otros objetos que deberían haber cambiado, teniendo en cuenta que los índices pueden haberse movido.


[<> isEditing] se puede usar para determinar si la edición de la tabla está habilitada o no. En lugar de retrasarlo como se sugiere mediante el uso de la siguiente declaración

[table performSelector: @selector (reloadData) withObject: nil afterDelay: 0.02];


- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath{ [self.pairs exchangeObjectAtIndex:sourceIndexPath.row withObjectAtIndex:destinationIndexPath.row]; [self performSelector:@selector(reloadData) withObject:nil afterDelay:0.02]; } - (void)reloadData{ [table reloadData]; }

La mesa no puede volver a cargar mientras se está moviendo, vuelve a cargar después de un retraso y estarás bien.