UITableView delete / add row provoca CoreData: Grave Error de aplicación si se ha seleccionado otro object en el MasterView de un SplitViewController

Actualización 18/3 # 2. Comencé a contar beginUpdates y EndUpdates para asegurarme de que estén parejos. Justo antes de que haya una exception, se salen de synchronization. No estoy seguro de por qué.

ACTUALIZACIÓN 18/3: Creo que encontré el problema , pero no estoy seguro si sé cómo solucionarlo. Después de experimentar durante un par de horas, descubrí que solo podía bloquear la aplicación cuando había seleccionado más de un elemento en la vista de tabla maestra del svc durante esa session. Cuando se selecciona otro elemento en la vista de tabla maestra, la vista de tabla de detalles obtiene un nuevo set de objects y se invocan los elementos de restauración AUN SI ESTÁ a la mitad de un ciclo de actualización / movimiento / eliminación / inserción. De ahí el problema; Creo que tiene instrucciones para actualizar el object de historia antiguo, aunque se haya establecido un nuevo object de historia en el controller de vista de detalle.

¿Cómo puedo hacer que las actualizaciones de la animation / conetworkingata / tableview se procesen por completo antes de configurar una nueva historia en la vista de detalle? Guardé los cambios en managedObjectContext cuando se configuró == NO. Supongo que necesito crear un setStory personalizado que procese todas las actualizaciones del set UITableView / CoreData antes de aceptar el nuevo object.

Este código se invoca en el controller master tableview del svc en didSelectRowAtIndexPath:

[detailViewController setStory:storySet]; //where storySet is the new story object [detailViewController refreshTables]; 

Tengo errores intermitentes al intentar eliminar la fila donde la acción no se animará y la aplicación se cuelga esencialmente con el siguiente error (la fila se elimina del set CoreData). Esto sucede si he elegido más de una fila del controller maestro de tabla en el svc en una session.

Después de search en Google pensé que podría ser un problema con el controller (vacío): controller (NSFetchedResultsController *) didChangeObject: (id) que se invoca después de una actualización para animar los cambios que el usuario ha realizado.

¿Cómo puedo solucionar estos errores intermitentes?

  • 16 / 3He intentado realmente simplificar mi código. He eliminado todas las llamadas al context de object gestionado y las puse en setEditing, he eliminado superfluous [self.tableview reloadData] y [self.tableview setneedsdisplay] e invalidado el 'reordering' bool completamente (todavía está en el código, pero siempre es configurado en NO, por lo que no hace ninguna diferencia). El UITableView es más estable que nunca, pero aún así consigo get errores intermitentes al eliminar (y de vez en cuando agregar); parece que tarda un poco en fallar, pero aún así lo hará. **
  • 15/3: Creo que tiene algo que ver con que CoreData / UITableView no esté sincronizado – CoreData cree que tiene less o más que UITableView
  • No hay problema con el set CoreData, es solo el lado de animation / interfaz de usuario de las cosas (las cosas se eliminan)
  • Es intermitente, solo en algunas eliminaciones.
  • Después de una cierta ayuda de railwayparade implementé NSFetchedResultsChangeMove en didChangeObject: que solucionó el error en movimiento, pero no los errores de eliminación.

¿Alguien puede ver algo que me he perdido o que puedo verificar? Feliz de dar más información si eso ayuda a resolver el problema.

Disculpas por la cantidad obscena de código publicado aquí.

El error:

CoreData: error: error grave de la aplicación. Se capturó una exception del delegado de NSFetchedResultsController durante una llamada a -controllerDidChangeContent :. intente insert la fila 3 en la sección 0, pero solo hay 3 filas en la sección 0 después de la actualización con userInfo (nulo)

 // // MakeSentenceTableViewController.h // StoryBot // // Created by Glen Storey on 25/10/10. // Copyright 2010 Glen Storey. All rights reserved. // #import <UIKit/UIKit.h> #import "AddStoryItem.h" #import "Story.h" #import "Sentence.h" @interface MakeSentenceTableViewController : UITableViewController <NSFetchedResultsControllerDelegate, AddStoryItemDelegate, UINavigationControllerDelegate, UIImagePickerControllerDelegate, UITextFieldDelegate, UIPopoverControllerDelegate> { NSManagedObjectContext *managedObjectContext; NSFetchedResultsController *fetchedResultsController; UIPopoverController *popoverController; UIBarButtonItem *playButtonItem; UIBarButtonItem *addButtonItem; BOOL reordering; BOOL insert; BOOL delete; BOOL move; int beginUpdatesCount; int endUpdatesCount; } @property (nonatomic, retain) Story *story; @property (nonatomic, retain) NSManagedObjectContext *managedObjectContext; @property (nonatomic, retain) UIBarButtonItem *playButtonItem; @property (nonatomic, retain) UIBarButtonItem *addButtonItem; @property BOOL reordering, insert, delete, move; -(IBAction)createStoryModal:(id)sender; -(void)refreshTables; -(IBAction)pushShare:(id)sender; @end // // MakeSentenceTableViewController.m // // // Created by Glen Storey on 25/10/10. // Copyright 2010 Glen Storey. All rights reserved. // #import "MakeSentenceTableViewController.h" #import "ShareViewController.h" #import "StoryBotAppDelegate.h" @implementation MakeSentenceTableViewController @synthesize story, managedObjectContext, addButtonItem, playButtonItem, reordering, insert, delete, move; -(void)addStoryItemAction:(NSString*)text order:(NSNumber*)order image:(NSString*)image thumb:(NSString*)thumb{ NSLog(@"Text: %@, Order: %@, Image: %@.", text, order, image); NSLog(@"beginUpdatesCount: %d vs. endUpdatescount: %d", beginUpdatesCount, endUpdatesCount); NSSet *sentences = [story sentences]; NSNumber *maxOrder = [sentences valueForKeyPath:@"@max.order"]; NSLog(@"maxOrder: %@", maxOrder); if(maxOrder == 0){ maxOrder = [[NSNumber alloc] initWithInteger: 0]; } //make a new sentence! Sentence *sentence = [NSEntityDescription insertNewObjectForEntityForName:@"Sentence" inManagedObjectContext:managedObjectContext]; [sentence setText: text]; [sentence setImage: image]; [sentence setThumb: thumb]; [sentence setBelongsTo: story]; if([maxOrder intValue] >= 1 ){ [sentence setOrder: [[NSNumber alloc] initWithInteger:[maxOrder intValue]+1]]; }else{ [sentence setOrder: [[NSNumber alloc] initWithInteger:1]]; } NSMutableSet *mutableSet = [[NSMutableSet alloc] initWithSet:sentences]; [mutableSet addObject:sentence]; //NSLog(@"sentences before setWithSet %@", mutableSet); sentences = [[NSSet alloc] initWithSet: mutableSet]; //NSLog(@"sentences after setWithSet %@", sentences); [story setSentences:sentences]; //NSError *error; //BOOL isSaved = [managedObjectContext save:&error]; //NSLog(@"isSaved? %@", (isSaved ? @"YES" :@"NO ") ); //if (!isSaved) { //NSLog(@"%@:%s Error saving context: %@", [self class], _cmd, [error localizedDescription]); //Don't worry about this warning - just rem it out when finished (just a log) // return; //} [sentences release]; [mutableSet release]; //[self.tableView reloadData]; //[self.tableView setNeedsDisplay]; [self dismissModalViewControllerAnimated:YES]; } #pragma mark - #pragma mark View lifecycle -(id)initWithNibName:(NSString*)name bundle:(NSBundle*)bundle; { self = [super initWithNibName:name bundle:bundle]; if (self) { self.title = @"My Stories"; addButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd target:self action:@selector(createStoryModal:)]; playButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemPlay target:self action:@selector(pushShare:)]; if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { [addButtonItem setEnabled:NO]; [playButtonItem setEnabled:NO]; } NSArray* toolbarItems = [NSArray arrayWithObjects: addButtonItem, [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemFlexibleSpace target:nil action:nil], playButtonItem, nil]; [toolbarItems makeObjectsPerformSelector:@selector(release)]; self.toolbarItems = toolbarItems; //NSLog(@"self: %@ self.toolbarItems: %@", self, self.toolbarItems); //Do I need to release UIBarButtonItems? NSLog(@"initWithNibName:"); } return self; } -(void)refreshTables{ //use this to refresh for new table content. Also calls self.tableview reloadData so you don't need to call it afterward. NSLog(@"===================================refreshTables"); NSLog(@"story object %@", story); if (managedObjectContext == nil) { NSLog(@"managedObjectContext == nil"); managedObjectContext = [(StoryBotAppDelegate *)[[UIApplication shanetworkingApplication] delegate] managedObjectContext]; NSLog(@"After managedObjectContext: %@", managedObjectContext); }else{ NSLog(@"managedObjectContext != nil"); } NSFetchRequest *request = [[NSFetchRequest alloc] init]; NSEntityDescription *entity = [NSEntityDescription entityForName:@"Sentence" inManagedObjectContext:managedObjectContext]; [request setEntity:entity]; //sorting stuff: NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"order" ascending: YES]; NSArray *sortDescriptors = [[NSArray alloc] initWithObjects: sortDescriptor, nil]; [request setSortDescriptors:sortDescriptors]; [sortDescriptors release]; [sortDescriptor release]; NSPnetworkingicate *pnetworkingicateTitle = [NSPnetworkingicate pnetworkingicateWithFormat:@"belongsTo=%@",story]; [request setPnetworkingicate :pnetworkingicateTitle]; NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease]; [dateFormatter setDateFormat:@"EEE, d MMM yyyy HH:mm:ss"]; NSString *dateString = [dateFormatter stringFromDate:[story creationDate]]; NSLog(@"dateString: %@", dateString); fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:dateString]; fetchedResultsController.delegate = self; [request release]; NSError *error; [fetchedResultsController performFetch:&error]; [self.tableView reloadData]; } - (void)viewDidLoad { [super viewDidLoad]; self.title = @"My Story"; NSLog(@"Passed Story Object: %@", story); //NSLog(@"managedObjectContext: %@", managedObjectContext); //Need a pnetworkingicate for belongsTo here. self.tableView.rowHeight = 50; if(story != NULL){ if (managedObjectContext == nil) { NSLog(@"managedObjectContext == nil"); managedObjectContext = [(StoryBotAppDelegate *)[[UIApplication shanetworkingApplication] delegate] managedObjectContext]; NSLog(@"After managedObjectContext: %@", managedObjectContext); }else{ NSLog(@"managedObjectContext != nil"); } NSFetchRequest *request = [[NSFetchRequest alloc] init]; NSEntityDescription *entity = [NSEntityDescription entityForName:@"Sentence" inManagedObjectContext:managedObjectContext]; [request setEntity:entity]; //sorting stuff: NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"order" ascending: YES]; NSArray *sortDescriptors = [[NSArray alloc] initWithObjects: sortDescriptor, nil]; [request setSortDescriptors:sortDescriptors]; [sortDescriptors release]; [sortDescriptor release]; NSPnetworkingicate *pnetworkingicateTitle = [NSPnetworkingicate pnetworkingicateWithFormat:@"belongsTo=%@",story]; [request setPnetworkingicate :pnetworkingicateTitle]; fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:request managedObjectContext:managedObjectContext sectionNameKeyPath:nil cacheName:nil]; fetchedResultsController.delegate = self; [request release]; NSError *error; [fetchedResultsController performFetch:&error]; } #pragma mark - #pragma mark Table view data source - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { // Return the number of sections. return 1; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { //Return the number of rows in the section. NSArray *sections = [fetchedResultsController sections]; NSInteger count = 0; if ([sections count]){ id <NSFetchedResultsSectionInfo> sectionInfo = [sections objectAtIndex:section]; count = [sectionInfo numberOfObjects]; } NSLog(@"# of rows in section %d", count); return count; } // Customize the appearance of table view cells. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (cell == nil) { cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease]; } // Configure the cell... Sentence *sentenceAtCell = [fetchedResultsController objectAtIndexPath:indexPath]; //NSLog(@"sentenceAtCell: %@", sentenceAtCell); cell.textLabel.text = [sentenceAtCell text]; NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *uniquePath = [[paths objectAtIndex:0] stringByAppendingPathComponent:[sentenceAtCell thumb]]; // This should crop it as you want - you've just got to create cropRect. UIImage *largeImage = [UIImage imageWithContentsOfFile: uniquePath]; CGRect cropRect = CGRectMake(0, 0, 66, 50); /*if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)]) { if ([[UIScreen mainScreen] scale] == 2) { // running an iPhone 4 or equiv. res device. cropRect = CGRectMake(15, 14, 100, 75); } }*/ CGImageRef imageRef = CGImageCreateWithImageInRect([largeImage CGImage], cropRect); cell.imageView.image = [UIImage imageWithCGImage: imageRef]; CGImageRelease(imageRef); //NSLog(@"indexPath: %@", indexPath); return cell; } // Override to support editing the table view. - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { if (editingStyle == UITableViewCellEditingStyleDelete) { NSLog(@"Delete row"); NSFileManager *fileManager = [NSFileManager defaultManager]; NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDirectoryPath = [paths objectAtIndex:0]; // 1. Look at the sentence we're about to delete. Sentence *sentenceRetire = [fetchedResultsController objectAtIndexPath:indexPath]; // 2. Does it have an order of 0? NSLog(@"=================== sentenceRetire: %@", sentenceRetire); if([[sentenceRetire order] intValue] == 0){ // YES: Is there another sentence in this story? Story *storyRetire = [sentenceRetire belongsTo]; NSSet *sentencesInRetinetworkingSentenceSet = [storyRetire sentences]; if ([sentencesInRetinetworkingSentenceSet count] > 1){ // YES: Set the sentence with the smallest order to an order of 0 // then delete the sentence + files NSPnetworkingicate *pnetworkingicateTitle = [NSPnetworkingicate pnetworkingicateWithFormat:@"order>0"]; NSSet *sentencesWithPotentialToBeTitle = [sentencesInRetinetworkingSentenceSet filtenetworkingSetUsingPnetworkingicate:pnetworkingicateTitle]; NSNumber *minOrder = [sentencesWithPotentialToBeTitle valueForKeyPath:@"@min.order"]; NSPnetworkingicate *pnetworkingicateOrder = [NSPnetworkingicate pnetworkingicateWithFormat:@"order=%d",[minOrder intValue]]; NSSet *sentenceWithPotentialToBeTitle = [sentencesWithPotentialToBeTitle filtenetworkingSetUsingPnetworkingicate:pnetworkingicateOrder]; //note the sentence (singular) not sentences. This is the sentence who's order needs to be updated. NSLog(@"setenceWithPotentialToBeTitle (check order isn't null on crash): %@", sentenceWithPotentialToBeTitle); Sentence *sentenceToPromote = [sentenceWithPotentialToBeTitle anyObject]; //now we know which sentence to promote we can delete the sentence & Files. [managedObjectContext deleteObject:[fetchedResultsController objectAtIndexPath:indexPath]]; NSString *imageTrash = [documentsDirectoryPath stringByAppendingPathComponent:(NSString*)[sentenceRetire image]]; NSString *thumbTrash = [documentsDirectoryPath stringByAppendingPathComponent:[sentenceRetire thumb]]; NSLog(@"About to delete these files: %@, %@", imageTrash, thumbTrash); [fileManager removeItemAtPath:imageTrash error:NULL]; [fileManager removeItemAtPath:thumbTrash error:NULL]; //and promote the new title. [sentenceToPromote setOrder:[[NSNumber alloc] initWithInteger:0]]; }else{ // NO: We're deleting this story // Delete the files! [managedObjectContext deleteObject:storyRetire]; NSString *imageTrash = [documentsDirectoryPath stringByAppendingPathComponent:(NSString*)[sentenceRetire image]]; NSString *thumbTrash = [documentsDirectoryPath stringByAppendingPathComponent:[sentenceRetire thumb]]; //NSLog(@"About to delete these files: %@, %@", imageTrash, thumbTrash); [fileManager removeItemAtPath:imageTrash error:NULL]; [fileManager removeItemAtPath:thumbTrash error:NULL]; //Pop back. if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { NSLog(@"last sentence in story - delete that story and point, somewhere!"); //probably delete the sentece to clear the table, then delete the story using storyRetire } else{ [self.navigationController popViewControllerAnimated:YES]; } } }else{ // NO: Delete the Sentence Object. [managedObjectContext deleteObject:[fetchedResultsController objectAtIndexPath:indexPath]]; NSString *imageTrash = [documentsDirectoryPath stringByAppendingPathComponent:(NSString*)[sentenceRetire image]]; NSString *thumbTrash = [documentsDirectoryPath stringByAppendingPathComponent:[sentenceRetire thumb]]; //NSLog(@"About to delete these files: %@, %@", imageTrash, thumbTrash); [fileManager removeItemAtPath:imageTrash error:NULL]; [fileManager removeItemAtPath:thumbTrash error:NULL]; } // Save the context. //NSError *error; //if (![managedObjectContext save:&error]) { /* Replace this implementation with code to handle the error appropriately. abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button. */ // NSLog(@"Unresolved error %@, %@", error, [error userInfo]); // abort(); //} } } - (void)setEditing:(BOOL)editing animated:(BOOL)animated { [super setEditing:editing animated:animated]; if (!editing) { //save here NSError *error; BOOL isSaved = [managedObjectContext save:&error]; NSLog(@"isSaved? %@ ======================================", (isSaved ? @"YES" :@"NO ") ); } } // Override to support rearranging the table view. - (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath { //this implementation is from here: http://www.cimgf.com/2010/06/05/re-ordering-nsfetchedresultscontroller/ NSMutableArray *things = [[fetchedResultsController fetchedObjects] mutableCopy]; // Grab the item we're moving. NSManagedObject *thing = [fetchedResultsController objectAtIndexPath:fromIndexPath]; // Remove the object we're moving from the array. [things removeObject:thing]; // Now re-insert it at the destination. [things insertObject:thing atIndex:[toIndexPath 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:@"order"]; } NSLog(@"things: %@", things); [things release], things = nil; //reordering = YES; //NSLog(@"moveRowAtIndexPath: IS reordering"); //NSError *error; //[managedObjectContext save:&error]; } #pragma mark - #pragma mark Table view delegate /** Delegate methods of NSFetchedResultsController to respond to additions, removals and so on. */ - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller { // The fetch controller is about to start sending change notifications, so prepare the table view for updates. NSLog(@"controllerWillChangeContent: BeginUpdates"); [self.tableView beginUpdates]; beginUpdatesCount++; NSLog(@"====================beginUpdates was just incremented"); NSLog(@"beginUpdatesCount %d", beginUpdatesCount); NSLog(@"endUpdatesCount %d", endUpdatesCount); } - (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath { NSLog(@"didChangeObject: %@", anObject); UITableView *tableView; tableView = self.tableView; switch(type) { case NSFetchedResultsChangeInsert: NSLog(@"ResultsChangeInsert:"); insert = YES; [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; break; case NSFetchedResultsChangeDelete: NSLog(@"ResultsChangeDelete:"); delete = YES; [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; break; case NSFetchedResultsChangeMove: NSLog(@"ResultsChangeMove:"); move = YES; [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone]; [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationNone]; break; default: NSLog(@"switch problem - default"); } } - (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type { NSLog(@"didChangeSection:"); switch(type) { case NSFetchedResultsChangeInsert: [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade]; break; case NSFetchedResultsChangeDelete: [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade]; break; } } - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { NSLog(@"didChangeContent"); // The fetch controller has sent all current change notifications, so tell the table view to process all updates. NSLog(@"almost endUpdates=============================================="); if(delete){ NSLog(@"endUpdates delete"); delete = NO; } if(move){ NSLog(@"endUpdates move"); move = NO; } if(insert){ NSLog(@"endUpdates insert"); insert = NO; } [self.tableView endUpdates]; endUpdatesCount++; NSLog(@"====================endUpdates was just incremented"); NSLog(@"endUpdatesCount %d", endUpdatesCount); NSLog(@"beginUpdatesCount %d", beginUpdatesCount); NSLog(@"endUpdates finished"); } #pragma mark - #pragma mark Memory management - (void)dealloc { NSLog(@"Dealloc Sentence"); //[fliteEngine stopTalking]; //[fliteEngine release]; addButtonItem = nil; playButtonItem = nil; if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) { //It doesn't seem to get allocated because it's not set on the iPhone. [addButtonItem release]; [playButtonItem release]; } [story release]; [fetchedResultsController release]; [super dealloc]; } @end 

Actualización 18/3 # 3 Leí esta pregunta sobre un fetchedResultsController que necesita tener su delegado configurado en cero antes de crear uno nuevo. pongo

 detailViewController.fetchedResultsController = nil; detailViewController.fetchedResultsController.delegate = nil; 

en la vista maestra de SVC en didselectrowatindexpath y el problema se ha detenido. RefreshTables creó un nuevo fetchedResultsController cada vez que se seleccionó una fila en la vista maestra, y creo que seguían interfiriendo incluso cuando se seleccionó un nuevo object de historia.

Te perdiste la implementación de NSFetchedResultsChangeMove en didChangeObject:

Aquí está mi código que hace lo mismo:

 - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller { sectionHeadersChanged=NO; [self._tableView beginUpdates]; } - (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type { switch(type) { case NSFetchedResultsChangeInsert: [self._tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationAutomatic]; break; case NSFetchedResultsChangeDelete: [self._tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationAutomatic]; break; } sectionHeadersChanged=YES; } - (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:UITableViewRowAnimationAutomatic]; break; case NSFetchedResultsChangeDelete: [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; break; case NSFetchedResultsChangeUpdate: [tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; break; case NSFetchedResultsChangeMove: [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationNone]; [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationBottom]; break; } } - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { [self._tableView endUpdates]; //reload all sections if(sectionHeadersChanged==YES){ [self._tableView reloadSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, self._tableView.numberOfSections)] withRowAnimation:UITableViewRowAnimationNone]; } }