2009-10-30 8 views
9

Tengo problemas para hacer que mis entidades de datos centrales jueguen bien y comprendan cuando utilizo una UITableView.¿Cómo puedo mantener el orden de visualización en UITableView utilizando Core Data?

He estado a través de una serie de tutoriales y otras preguntas aquí en StackOverflow, pero no parece haber una manera clara o elegante de hacer esto - Realmente espero que me falta algo.

Tengo una sola entidad de datos centrales que tiene un atributo int16 llamado "displayOrder". Uso un NSFetchRequest que se ha ordenado en "displayOrder" para devolver los datos de mi UITableView. Se está respetando todo menos reordenar. Aquí está mi (ineficiente) moveRowAtIndePath método:

- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {   

    NSUInteger fromIndex = fromIndexPath.row; 
    NSUInteger toIndex = toIndexPath.row; 

    FFObject *affectedObject = [self.fetchedResultsController.fetchedObjects objectAtIndex:fromIndex]; 
    affectedObject.displayOrderValue = toIndex; 

    [self FF_fetchResults]; 


    for (NSUInteger i = 0; i < [self.fetchedResultsController.fetchedObjects count]; i++) { 
     FFObject *otherObject = [self.fetchedResultsController.fetchedObjects objectAtIndex:i]; 
     NSLog(@"Updated %@/%@ from %i to %i", otherObject.name, otherObject.state, otherObject.displayOrderValue, i); 
     otherObject.displayOrderValue = i; 
    } 

    [self FF_fetchResults]; 
} 

¿Puede alguien me punto en la dirección de una buena parte de código de ejemplo, o ver lo que estoy haciendo mal? La visualización de la tabla de visualización se actualiza correctamente, y puedo ver a través de mis mensajes de registro que la propiedad displayOrder se está actualizando. Simplemente no se está guardando y recargando constantemente, y algo se siente "mal" con respecto a esta implementación (aparte de la iteración derrochadora de todos mis FFObjects).

Gracias de antemano por cualquier consejo que pueda prestar.

Respuesta

27

Me tomó un vistazo a su código y esto podría funcionar mejor:

- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {   

    NSUInteger fromIndex = fromIndexPath.row; 
    NSUInteger toIndex = toIndexPath.row; 

    if (fromIndex == toIndex) { 
     return; 
    } 

    FFObject *affectedObject = [self.fetchedResultsController.fetchedObjects objectAtIndex:fromIndex]; 
    affectedObject.displayOrderValue = toIndex; 

    NSUInteger start, end; 
    int delta; 

    if (fromIndex < toIndex) { 
     // move was down, need to shift up 
     delta = -1; 
     start = fromIndex + 1; 
     end = toIndex; 
    } else { // fromIndex > toIndex 
     // move was up, need to shift down 
     delta = 1; 
     start = toIndex; 
     end = fromIndex - 1; 
    } 

    for (NSUInteger i = start; i <= end; i++) { 
     FFObject *otherObject = [self.fetchedResultsController.fetchedObjects objectAtIndex:i]; 
     NSLog(@"Updated %@/%@ from %i to %i", otherObject.name, otherObject.state, otherObject.displayOrderValue, otherObject.displayOrderValue + delta); 
     otherObject.displayOrderValue += delta; 
    } 

    [self FF_fetchResults]; 
} 
+0

Brilliant! Esto funcionó perfectamente. Estuve mirando el código original durante mucho tiempo, gracias por su ayuda. –

+4

Esto debe implementarse en paralelo con http://stackoverflow.com/questions/1077568/how-to-implement-re-ordering-of-coredata-records/2013070#2013070 –

+0

Si se pueden eliminar elementos, esto no trabajo. Debe asegurarse de actualizar el campo de orden también al eliminar una fila. – Kamchatka

2

(Esto pretende ser como un comentario sobre la respuesta de gerry3 anterior, pero todavía no soy capaz de hacer comentarios sobre cuestiones de otros usuarios y respuestas.)

Una pequeña mejora para la solución de gerry3 - muy elegante. Si no me equivoco, la línea

otherObject.displayOrderValue += delta; 

realidad realizará puntero aritmética si displayOrderValue no es de tipo primitivo. Que puede no ser lo que quieres. En su lugar, para establecer el valor de la entidad, propongo:

otherObject.displayOrderValue = [NSNumber numberWithInt:[otherObject.displayOrderValue intValue] + delta]; 

Esto debería actualizar su propiedad de entidad correcta y evitar accidentes EXC_BAD_ACCESS.

+1

displayOrder es un atributo de entidad Data Core (así, NSNumber) - Estoy usando mogenerator, que crea automáticamente acceso a valores primitivos con el sufijo "Value", por lo que en este caso la aritmética del puntero está perfectamente bien. Gracias por señalar la trampa;) –

+1

np and ... ¡Gracias por señalarme hacia mogenerator! – octy

0

creo que:

affectedObject.displayOrderValue = toIndex; 

deben ponerse a continuación:

for (NSUInteger i = start; i <= end; i++) { 
    FFObject *otherObject = [self.fetchedResultsController.fetchedObjects objectAtIndex:i]; 
    NSLog(@"Updated %@/%@ from %i to %i", otherObject.name, otherObject.state, otherObject.displayOrderValue, otherObject.displayOrderValue + delta); 
    otherObject.displayOrderValue += delta; 
} 

y antes:

[self FF_fetchResults]; 
1

Aquí una solución completa de cómo manejar una tabla indexada con los datos básicos . Su atributo se llama displayOrder, lo llamo index. Antes que nada, es mejor separar el controlador y el modelo de vista. Para esto utilizo un controlador modelo, que es la interfaz entre la vista y el modelo.

Hay 3 casos que necesita para administrar que el usuario puede influir a través del controlador de vista.

  1. Adición de un nuevo objeto
  2. Eliminación de un objeto existente
  3. objetos Reordenar.

Los primeros dos casos de Agregar y eliminar son bastante sencillos. Eliminar llama a una rutina llamada renewObjectIndicesUpwardsFromIndex para actualizar los índices después del objeto eliminado.

- (void)createObjectWithTitle:(NSString*)title { 
    FFObject* object = [FFObject insertIntoContext:self.managedObjectContext]; 

    object.title = title; 
    object.index = [NSNumber numberWithInteger:[self numberTotalObjects]]; 
    [self saveContext]; 
} 

- (void)deleteObject:(FFObject*)anObject { 
    NSInteger objectIndex = [anObject.index integerValue]; 
    [anObject deleteObject]; 
    [self renewObjectIndicesUpwardsFromIndex:objectIndex]; 
    [self saveContext]; 
} 

- (void)renewObjectIndicesUpwardsFromIndex:(NSInteger)fromIndex { 
    NSFetchRequest* fetchRequest = [[NSFetchRequest alloc] init]; 
    [fetchRequest setEntity:[NSEntityDescription entityForName:@"Object" inManagedObjectContext:self.managedObjectContext]]; 

    NSPredicate* predicate = [NSPredicate predicateWithFormat:@"(index > %d)", fromIndex]; 
    [fetchRequest setPredicate:predicate]; 

    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"index" ascending:YES]; 
    NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil]; 

    [fetchRequest setSortDescriptors:sortDescriptors]; 

    NSError* fetchError = nil; 
    NSArray* objects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&fetchError]; 

    NSInteger index = fromIndex; 
    for (FFObject* object in objects) { 
    object.index = [NSNumber numberWithInteger:index]; 
    index += 1; 
    } 
    [self saveContext]; 
} 

Antes de pasar a las rutinas de controlador para la re-fin, aquí la pieza en el controlador de vista. Yo uso un bool isModifyingOrder similar a this answer. Tenga en cuenta que el controlador de vista llama a dos funciones en el controlador moveObjectOrderUp y moveObjectOrderDown. Dependiendo de cómo muestre los objetos en la vista de tabla (la más nueva primero o la última), puede cambiarlos.

- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath toIndexPath:(NSIndexPath *)destinationIndexPath { 

    isModifyingOrder = YES; 

    NSUInteger fromIndex = sourceIndexPath.row; 
    NSUInteger toIndex = destinationIndexPath.row; 

    if (fromIndex == toIndex) { 
    return; 
    } 

    FFObject *affectedObject = [self.fetchedResultsController.fetchedObjects objectAtIndex:fromIndex]; 

    NSInteger delta; 
    if (fromIndex < toIndex) { 
    delta = toIndex - fromIndex; 
    NSLog(@"Moved down by %lu cells", delta); 
    [self.objectController moveObjectOrderUp:affectedObject by:delta]; 
    } else { 
    delta = fromIndex - toIndex; 
    NSLog(@"Moved up by %lu cells", delta); 
    [self.objectController moveObjectOrderDown:affectedObject by:delta]; 
    } 

    isModifyingOrder = NO; 
} 

Y aquí la parte en el controlador. Esto se puede escribir mejor, pero para entender esto es quizás lo mejor.

- (void)moveObjectOrderUp:(FFObject*)affectedObject by:(NSInteger)delta { 
    NSInteger fromIndex = [affectedObject.index integerValue] - delta; 
    NSInteger toIndex = [affectedObject.index integerValue]; 

    if (fromIndex < 1) { 
    return; 
    } 

    NSFetchRequest* fetchRequest = [[NSFetchRequest alloc] init]; 
    [fetchRequest setEntity:[NSEntityDescription entityForName:@"Object" inManagedObjectContext:self.managedObjectContext]]; 

    NSPredicate* predicate = [NSPredicate predicateWithFormat:@"(index >= %d) AND (index < %d)", fromIndex, toIndex]; 
    [fetchRequest setPredicate:predicate]; 

    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"index" ascending:YES]; 
    NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil]; 

    [fetchRequest setSortDescriptors:sortDescriptors]; 

    NSError* fetchError = nil; 
    NSArray* objects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&fetchError]; 

    for (FFObject* object in objects) { 
    NSInteger newIndex = [object.index integerValue] + 1; 
    object.index = [NSNumber numberWithInteger:newIndex]; 
    } 

    affectedObject.index = [NSNumber numberWithInteger:fromIndex]; 

    [self saveContext]; 
} 

- (void)moveObjectOrderDown:(FFObject*)affectedObject by:(NSInteger)delta { 
    NSInteger fromIndex = [affectedObject.index integerValue]; 
    NSInteger toIndex = [affectedObject.index integerValue] + delta; 

    NSFetchRequest* fetchRequest = [[NSFetchRequest alloc] init]; 
    [fetchRequest setEntity:[NSEntityDescription entityForName:@"Object" inManagedObjectContext:self.managedObjectContext]]; 

    NSPredicate* predicate = [NSPredicate predicateWithFormat:@"(index > %d) AND (index <= %d)", fromIndex, toIndex]; 
    [fetchRequest setPredicate:predicate]; 

    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"index" ascending:YES]; 
    NSArray *sortDescriptors = [NSArray arrayWithObjects:sortDescriptor, nil]; 

    [fetchRequest setSortDescriptors:sortDescriptors]; 

    NSError* fetchError = nil; 
    NSArray* objects = [self.managedObjectContext executeFetchRequest:fetchRequest error:&fetchError]; 

    for (FFObject* object in objects) 
    { 
    NSInteger newIndex = [object.index integerValue] - 1; 
    object.index = [NSNumber numberWithInteger:newIndex]; 
    } 

    affectedObject.index = [NSNumber numberWithInteger:toIndex]; 

    [self saveContext]; 
} 

No se olvide de utilizar un segundo BOOL en su controlador de vista de la acción de eliminación para evitar que la notificación del movimiento que hacer nada. Lo llamo isDeleting y lo coloco aquí.

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

    ... 

    switch(type) { 

    ... 

    case NSFetchedResultsChangeMove: 
     if (isDeleting == false) { 
      [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:localIndexPath] withRowAnimation:UITableViewRowAnimationFade]; 
      [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:localNewIndexPath]withRowAnimation:UITableViewRowAnimationFade]; 
     } 
     break; 

    ... 

    } 
} 
Cuestiones relacionadas