Ios UITableView删除/添加行导致CoreData:如果在SplitViewController的主视图中选择了另一个对象,则会出现严重的应用程序错误

Ios UITableView删除/添加行导致CoreData:如果在SplitViewController的主视图中选择了另一个对象,则会出现严重的应用程序错误,ios,cocoa-touch,ipad,uitableview,core-data,Ios,Cocoa Touch,Ipad,Uitableview,Core Data,更新18/3#2。我已经开始计算开始更新和结束更新,以确保它们相等。在出现异常之前,它们就不同步了。不知道为什么。 更新18/3:我想我已经发现了问题,但我不确定我是否知道如何解决它。经过几个小时的实验,我发现只有在会话期间在svc的主表视图中选择了多个项目时,我才能使应用程序崩溃。 在主表视图中选择另一项时,详图表视图将获得一个新的对象集,并调用refreshtables,即使它处于更新/移动/删除/插入周期的中途。因此,问题就出现了;我认为它有更新旧故事对象的指令,即使在detailview

更新18/3#2。我已经开始计算开始更新和结束更新,以确保它们相等。在出现异常之前,它们就不同步了。不知道为什么。

更新18/3:我想我已经发现了问题,但我不确定我是否知道如何解决它。经过几个小时的实验,我发现只有在会话期间在svc的主表视图中选择了多个项目时,我才能使应用程序崩溃。 在主表视图中选择另一项时,详图表视图将获得一个新的对象集,并调用refreshtables,即使它处于更新/移动/删除/插入周期的中途。因此,问题就出现了;我认为它有更新旧故事对象的指令,即使在detailviewcontroller上设置了新故事对象

在细节视图中设置新故事之前,如何使动画/coredata/tableview更新得到完全处理?当SetEditing==否时,我将更改保存到managedObjectContext。我想我需要制作一个自定义SetTory setter,在接受新对象之前处理对UITableView/CoreData集的所有更新?

此代码在didSelectRowAtIndexPath中的svc主tableview控制器上调用:

[detailViewController setStory:storySet]; //where storySet is the new story object
[detailViewController refreshTables];
我在尝试删除操作无法设置动画的行时出现了间歇性错误,并且应用程序实际上由于以下错误而挂起(尽管该行已从CoreData集中删除)。如果我在一个会话中从svc中的主tableview控制器中选择了多行,就会发生这种情况

在谷歌搜索之后,我想这可能是(void)controller:(NSFetchedResultsController*)controller didChangeObject:(id)的问题,在更新后调用它来设置用户所做更改的动画

如何修复这些间歇性错误

  • 16/3我试图真正简化我的代码。我已经删除了对托管对象上下文的所有调用,并将它们放在setEditing中,删除了多余的[self.tableview reloadData]和[self.tableview setneedsdisplay]并完全使“reordering”bool无效(它仍在代码中,但它总是设置为NO,因此没有区别)。UITableView比以往任何时候都更稳定,但我仍然设法在删除时(偶尔在添加时)出现间歇性错误—崩溃似乎需要一段时间,但仍然会**
  • 15/3:我认为这与CoreData/UITableView不同步有关——CoreData认为它比UITableView少或多
  • CoreData集没有问题,它只是事物的动画/UI方面(事物被移除)
  • 它是断断续续的,只是在某些情况下
  • 在railwayparade的帮助下,我在didChangeObject中实现了NSFetchedResultsChangeMove:它修复了移动错误,但没有修复删除错误
有人能看到我遗漏的或我可以检查的东西吗?如果这有助于解决问题,我很乐意提供更多信息

为这里发布的大量淫秽代码道歉

错误:

CoreData:错误:严重的应用程序错误。发现了一个异常 在调用时从NSFetchedResultsController的委托 -controllerDidChangeContent:。尝试将第3行插入节0,但在使用userInfo更新后,节0中只有3行 (空)

//
//MakeSentenceTableViewController.h
//故事机器人
//
//由格伦·斯托雷于2010年10月25日创作。
//版权所有2010格伦·斯托雷。版权所有。
//
#进口
#导入“AddStoryItem.h”
#导入“Story.h”
#输入“句子.h”
@接口MakeSentenceTableViewController:UITableViewController{
NSManagedObjectContext*managedObjectContext;
NSFetchedResultsController*fetchedResultsController;
UIPopoverController*popoverController;
UIBABUTTONITEM*playButtonItem;
UIBarButtonItem*addButtonItem;
布尔重排序;
布尔插入;
布尔删除;
布尔移动;
int开始更新;
int ENDDUPDATESCOUNT;
}
@财产(非原子,保留)故事*故事;
@属性(非原子,保留)NSManagedObjectContext*managedObjectContext;
@属性(非原子,保留)UIBarButtonItem*playButtonItem;
@属性(非原子,保留)UIBarButtonItem*addButtonItem;
@属性布尔重新排序、插入、删除、移动;
-(iAction)createStoryModal:(id)发送方;
-(无效)刷新表格;
-(iAction)pushShare:(id)发送方;
@结束
//
//MakeSentenceTableViewController.m
//  
//
//由格伦·斯托雷于2010年10月25日创作。
//版权所有2010格伦·斯托雷。版权所有。
//
#导入“MakeSentenceTableViewController.h”
#导入“ShareViewController.h”
#导入“StoryBotAppDelegate.h”
@实现MakeSentenceTableViewController
@综合故事、managedObjectContext、addButtonItem、playButtonItem、重新排序、插入、删除、移动;
-(void)addStoryItemAction:(NSString*)文本顺序:(NSNumber*)顺序图像:(NSString*)图像拇指:(NSString*)拇指{
NSLog(@“文本:%@,顺序:%@,图像:%@.”,文本,顺序,图像);
NSLog(@“beginUpdatesCount:%d vs.endUpdatescount:%d”,beginUpdatesCount,endUpdatescount);
NSSet*句子=[故事句子];
NSNumber*maxOrder=[句子值forkeyPath:@“@max.order”];
NSLog(@“maxOrder:%@”,maxOrder);
如果(maxOrder==0){
maxOrder=[[NSNumber alloc]initWithInteger:0];
}
//写一个新句子!
句子*句子=[NSEntityDescription insertNewObjectForEntityForName:@“句子”
inManagedObjectContext:managedObjectContext];
[句子设置文本:文本];
[句子集图像:im
//
//  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 sharedApplication] 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];

    NSPredicate *predicateTitle = [NSPredicate predicateWithFormat:@"belongsTo=%@",story];
    [request setPredicate :predicateTitle];

    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 predicate for belongsTo here.

    self.tableView.rowHeight = 50;

    if(story != NULL){
        if (managedObjectContext == nil) 
        { 
            NSLog(@"managedObjectContext == nil");
            managedObjectContext = [(StoryBotAppDelegate *)[[UIApplication sharedApplication] 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];

        NSPredicate *predicateTitle = [NSPredicate predicateWithFormat:@"belongsTo=%@",story];
        [request setPredicate :predicateTitle];



        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 *sentencesInRetiredSentenceSet    = [storyRetire sentences];


                if ([sentencesInRetiredSentenceSet count] > 1){
                    //          YES:    Set the sentence with the smallest order to an order of 0   
                    //                  then delete the sentence + files

                    NSPredicate *predicateTitle             = [NSPredicate predicateWithFormat:@"order>0"];
                    NSSet *sentencesWithPotentialToBeTitle  = [sentencesInRetiredSentenceSet filteredSetUsingPredicate:predicateTitle];
                    NSNumber *minOrder                      = [sentencesWithPotentialToBeTitle valueForKeyPath:@"@min.order"];  

                    NSPredicate *predicateOrder             = [NSPredicate predicateWithFormat:@"order=%d",[minOrder intValue]];
                    NSSet *sentenceWithPotentialToBeTitle   = [sentencesWithPotentialToBeTitle filteredSetUsingPredicate:predicateOrder];   

                    //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
- (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];
  }

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