objective c - Cambiar una propiedad de objeto gestionado no desencadena NSFetchedResultsController para actualizar la vista de tabla
objective-c ios (3)
Bien, explicaré su problema, entonces le dejaré juzgar si es un error en FRC o no. Si crees que es un error, entonces deberías presentar un informe de error con Apple.
El predicado del controlador de resultado de búsqueda es así:
NSString *predicate = [NSString stringWithFormat: @"clockSet.isOpen == YES"];
que es un predicado válido para un valor booleano. Seguirá la relación de la entidad clockSet
y isOpen
su atributo isOpen
. Si es YES
, esos objetos serán aceptados en la matriz de objetos.
Creo que estamos bien hasta aquí.
Ahora, si cambia uno de los atributos clockSet.isOpen
a NO
, entonces espera ver que ese objeto desaparezca de su vista de tabla (es decir, ya no debe coincidir con el predicado, por lo que debe eliminarse de la matriz de objetos recuperados).
Entonces, si tienes esto ...
[currentClockSet setIsOpen:[NSNumber numberWithBool:NO]];
entonces, cualquiera que sea el objeto de nivel superior que tenga una relación con currentClockSet
debería "desaparecer" de su matriz FRC de resultados obtenidos.
Sin embargo, no lo ves desaparecer. La razón es que el objeto monitoreado por el FRC no cambió. Sí, la ruta de clave de predicado cambió, pero la FRC contiene entidades de ClockPair
y una entidad ClockSet
realmente cambiada.
Puede ver las notificaciones volando para ver lo que ocurre detrás de escena.
De todos modos, el FRC utilizará una ruta clave cuando realice una búsqueda, pero no supervisará los cambios en los objetos que no están en su conjunto real de objetos recuperados.
La solución más fácil es "establecer" un atributo para el objeto que contiene este objeto de ruta de clave.
Por ejemplo, noté que el ClockPair
también tiene un atributo isOpen
. Si tienes una relación inversa, entonces podrías hacer esto ...
currentClockSet.isOpen = NO;
currentClockSet.clockPair.isOpen = currentClockSet.clockPair.isOpen;
Tenga en cuenta que en realidad no cambió el valor en absoluto. Sin embargo, se llamó al colocador, que desencadenó KVO y, por lo tanto, la notificación privada de DidChange, que luego le dijo al FRC que el objeto había cambiado. Por lo tanto, vuelve a evaluar el cheque para ver si el objeto debe incluirse, encuentra el valor de keypath cambiado y hace lo que espera.
Por lo tanto, si usa una ruta clave en su predicado FRC, si cambia ese valor, necesita regresar a todos los objetos en la matriz FRC y "ensuciarlos" para que esos objetos estén en la notificación que es pasado alrededor de los cambios de objetos. Es feo, pero probablemente sea mejor que guardar o cambiar tu solicitud de búsqueda y volver a buscarla.
Sé que no me crees, así que adelante, pruébalo. Tenga en cuenta que, para que funcione, debe saber qué elemento (s) en la matriz de objetos FRC se verán afectados por el cambio, y "meterlos" para que el FRC note el cambio.
La otra opción, como mencioné anteriormente, es guardar el contexto y volver a buscar los valores. Si no desea guardar el contexto, puede hacer que la búsqueda incluya actualizaciones en el contexto actual, sin refrescar desde la tienda.
Descubrí que simular un cambio en un objeto que está viendo el FRC es la mejor manera de realizar una reevaluación de los predicados que son rutas clave hacia otras entidades.
De acuerdo, entonces, si esto es un error o no, se debate. Personalmente, creo que si el FRC va a monitorear un keypath, debería hacerlo todo el camino, y no parcialmente como vemos aquí.
Espero que tenga sentido, y le animo a que presente un informe de error.
Tengo un control de resultados fetched con un predicado, donde "isOpen == YES"
Al llamar a closeCurrentClockSet , establezco esa propiedad en NO . Por lo tanto, ya no debería aparecer en mi tableView.
Por alguna razón, esto no está sucediendo.
¿Alguien me puede ayudar a resolver esto, por favor?
-(void)closeCurrentClockSet
{
NSPredicate * predicate = [NSPredicate predicateWithFormat:@"isOpen == YES"];
NSArray *fetchedObjects =
[self fetchRequestForEntity:@"ClockSet"
withPredicate:predicate
inManagedObjectContext:[myAppDelegate managedObjectContext]];
ClockSet *currentClockSet = (ClockSet *)fetchedObjects.lastObject;
[currentClockSet setIsOpen:[NSNumber numberWithBool:NO]];
}
-
Tengo un par de métodos más, usando exactamente el mismo enfoque , llamando a un método fetchRequestForEntity personalizado : withPredicate: inManagedObjectContext .
En esos métodos, al cambiar una propiedad, tableView se actualiza correctamente. Pero este arriba ( closeCurrentClockSet ), ¡no! No puedo entender por qué.
-
Mi implementación para my fetchedResultsController, proviene de la documentación de Apple.
Además, otro detalle. Si envío mi aplicación, al fondo. Ciérrelo y vuelva a abrir, ¡TableView muestra actualizado como debería!
He hecho mi mejor esfuerzo para seguir las preguntas anteriores aquí en stackOverflow. Sin suerte. También NSLogged esto al hueso. El objeto se está obteniendo correctamente. Es el correcto. La propiedad isOpen se está actualizando correctamente a NO . Pero por alguna razón, mi fetchedResultsController no actualiza tableView.
Intenté un par de soluciones "martillo", como reloadData y llamando a performFetch. Pero eso no funcionó. O tendría sentido usarlos ...
EDITAR: rayar eso, funcionó, llamando a reloadData imediatly después de performFetch en mi resultsController pero el uso de reloadData está dando una solución. Además, elimina todas las animaciones. Quiero que mi controlador actualice automáticamente mi TableView.
¿Puede alguien ayudarme a resolver esto?
¡Cualquier ayuda es muy apreciada!
Gracias,
Nuno
EDITAR:
La implementación completa.
fetchedResultsController es bastante estándar y directo. Todo lo demás es de la documentación de Apple
- (NSFetchedResultsController *)fetchedResultsController
{
if (_fetchedResultsController) {
return _fetchedResultsController;
}
NSManagedObjectContext * managedObjectContext = [myAppDelegate managedObjectContext];
NSEntityDescription *entity =
[NSEntityDescription entityForName:@"ClockPair"
inManagedObjectContext:managedObjectContext];
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:entity];
NSString *predicate = [NSString stringWithFormat: @"clockSet.isOpen == YES"];
[fetchRequest setPredicate: [NSPredicate predicateWithFormat:predicate]];
NSSortDescriptor *sortDescriptor1 =
[[NSSortDescriptor alloc] initWithKey:@"clockIn" ascending:NO];
NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor1, nil];
[fetchRequest setSortDescriptors:sortDescriptors];
[fetchRequest setFetchBatchSize:20];
NSFetchedResultsController *theFetchedResultsController =
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:managedObjectContext
sectionNameKeyPath:nil
cacheName:@"Root"];
_fetchedResultsController = theFetchedResultsController;
_fetchedResultsController.delegate = self;
return _fetchedResultsController;
}
-
Código repetitivo de la documentación de Apple:
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
{
// The fetch controller is about to start sending change notifications, so prepare the table view for updates.
[self.tableView beginUpdates];
}
- (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:UITableViewRowAnimationTop];
break;
case NSFetchedResultsChangeDelete:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationFade];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeMove:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
withRowAnimation:UITableViewRowAnimationLeft];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
withRowAnimation:UITableViewRowAnimationTop];
break;
}
}
- (void)controller:(NSFetchedResultsController *)controller
didChangeSection:(id )sectionInfo
atIndex:(NSUInteger)sectionIndex
forChangeType:(NSFetchedResultsChangeType)type
{
UITableView *tableView = self.tableView;
switch(type) {
case NSFetchedResultsChangeInsert:
[tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex]
withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[tableView 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.
[self.tableView endUpdates];
}
1ra ACTUALIZACIÓN:
El seguimiento [managedObjectContext hasChanges] devuelve SÍ, como debería. Pero fetchedResultsController no actualiza el tableView
2DA ACTUALIZACIÓN
didChangeObject: atIndexPath: ¡ no se llama en este caso particular! Tengo 2 métodos más, con el mismo código EXACTO, simplemente son una entidad diferente. Y funcionan perfectamente. Gracias @Leonardo por señalar esto
3º ACTUALIZAR este método, sigue las mismas reglas. Pero realmente funciona.
- (void)clockOut
{
NSPredicate * predicate = [NSPredicate predicateWithFormat:@"isOpen == %@", [NSNumber numberWithBool:YES]];
NSArray * fetchedObjects =
[self fetchRequestForEntity:@"ClockPair"
withPredicate:predicate
inManagedObjectContext:[myAppDelegate managedObjectContext]];
ClockPair *aClockPair = (ClockPair *)fetchedObjects.lastObject;
aClockPair.clockOut = [NSDate date];
aClockPair.isOpen = [NSNumber numberWithBool:NO];
}
¿Alguien tiene alguna otra idea sobre lo que me podría estar perdiendo?
Gracias,
Nuno
Después de [currentClockSet setIsOpen:[NSNumber numberWithBool:NO]];
¿Puedes guardar el contexto del objeto gestionado?
NSError *saveError = nil;
if( ![[myAppDelegate managedObjectContext] save:&saveError] ) {
// handle error saving context
}
Sospecho que su UITableView
se actualizará correctamente después de guardar el contexto. Es probable que el envío de su aplicación al fondo funcione. Sospecho que su pila de Datos Básicos está configurada en el delegado de la aplicación de tal forma que realiza un guardado en el NSManagedObjectContext
principal cuando pasa al segundo plano.
Te encontraste con un problema similar.
Sé que esta pregunta es bastante antigua, pero espero que esto ayude a alguien más:
La forma más fácil era introducir una nueva propiedad llamada lastUpdated: NSDate
en el objeto principal.
Tuve una Conversation
que contiene varios Messages
. Cada vez que se actualizaba el indicador isRead
del mensaje, necesitaba una actualización en ConversationOverviewViewController
que solo mostraba Conversation
. Además, NSFetchedResultsController
en ConversationOverviewVC
solo busca Conversation
y no sabe nada sobre un Message
.
Cada vez que se actualizaba un mensaje, llamaba a message.parentConversation.lastUpdated = NSDate()
. Es una forma fácil y útil de activar la actualización manualmente.
Espero que esto ayude.