47

This is an error:

CoreData: error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. attempt to delete and reload the same index path ( {length = 2, path = 0 - 0}) with userInfo (null)

This is my typical NSFetchedResultsControllerDelegate:

func controllerWillChangeContent(controller: NSFetchedResultsController) {
    tableView.beginUpdates()
}

func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {

    let indexSet = NSIndexSet(index: sectionIndex)

    switch type {
    case .Insert:
        tableView.insertSections(indexSet, withRowAnimation: .Fade)
    case .Delete:
        tableView.deleteSections(indexSet, withRowAnimation: .Fade)
    case .Update:
        fallthrough
    case .Move:
        tableView.reloadSections(indexSet, withRowAnimation: .Fade)
    }
}

func controller(controller: NSFetchedResultsController, didChangeObject anObject: NSManagedObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {

    switch type {
    case .Insert:
        if let newIndexPath = newIndexPath {
            tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade)
        }
    case .Delete:
        if let indexPath = indexPath {
            tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
        }
    case .Update:
        if let indexPath = indexPath {
            tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .None)
        }
    case .Move:
        if let indexPath = indexPath {
            if let newIndexPath = newIndexPath {
                tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
                tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade)
            }
        }
    }
}

func controllerDidChangeContent(controller: NSFetchedResultsController) {
    tableView.endUpdates()
}

in viewDidLoad():

private func setupOnceFetchedResultsController() {

    if fetchedResultsController == nil {
        let context = NSManagedObjectContext.MR_defaultContext()
        let fetchReguest = NSFetchRequest(entityName: "DBOrder")
        let dateDescriptor = NSSortDescriptor(key: "date", ascending: false)

        fetchReguest.predicate = NSPredicate(format: "user.identifier = %@", DBAppSettings.currentUser!.identifier )
        fetchReguest.sortDescriptors = [dateDescriptor]
        fetchReguest.fetchLimit = 10
        fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchReguest, managedObjectContext: context, sectionNameKeyPath: "identifier", cacheName: nil)
        fetchedResultsController.delegate = self

        try! fetchedResultsController.performFetch()
    }
}
Bartłomiej Semańczyk
  • 59,234
  • 49
  • 233
  • 358

6 Answers6

28

This seems to be a bug in iOS 9 (which is still beta) and is also discussed in the Apple Developer Forum

I can confirm the problem with the iOS 9 Simulator from Xcode 7 beta 3. I observed that for an updated managed object, the didChangeObject: delegate method is called twice: Once with the NSFetchedResultsChangeUpdate event and then again with the NSFetchedResultsChangeMove event (and indexPath == newIndexPath).

Adding an explicit check for indexPath != newIndexPath as suggested in the above thread seems to solve the problem:

        case .Move:
            if indexPath != newIndexPath {
                tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
                tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
        }
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • 1
    It solved my problem. But there is another problem when I add some rows: `attempt to delete row 0 from section 12, but there are only 10 sections before the update with userInfo (null)`. Do you think it is also a bug? – Bartłomiej Semańczyk Jul 13 '15 at 12:59
  • @BartłomiejSemańczyk: That might be a bug or an error in your code. That problem did not (yet) occur to me, so I can't tell. Did you check against the iOS 8 Simulator? – Martin R Jul 13 '15 at 13:06
  • No, it was working before, here with iOS 8.3 and Xcode7Beta3 it doesn't work. More over I do not try to delete anything, just adding some info... – Bartłomiej Semańczyk Jul 13 '15 at 13:20
  • @BartłomiejSemańczyk: Then it is *probably* an iOS 9 bug. I would suggest that you post a separate question where you can add all information which is necessary to reproduce that problem. – Martin R Jul 13 '15 at 13:25
  • I have the same error in Xcode 7 Beta 4, but explicit check for `indexPath != newIndexPath` is not helping now. Anyone else with a same problem? – tadija Jul 31 '15 at 09:38
  • 2
    Can you really compare 2 NSIndexPath instances for equality simply with "indexPath != newIndexPath"? Don't you need to compare the row and sections instead? Or use NSIndexPath.compare:? Or maybe isEqual? – Mark Krenek Aug 30 '15 at 19:06
  • @Mark: NSIndexPath inherits from NSObject, and the == operator for NSObjects uses the isEqual: method. Compare http://stackoverflow.com/a/28307428/1187415 for a similar issue. – Martin R Aug 30 '15 at 19:51
  • 6
    Still happens on XCode 7 GM build! – John Estropia Sep 10 '15 at 06:33
  • @MartinR in objective C that creates a problem. It need isEqual. – Kunal Balani Sep 14 '15 at 15:17
  • @KunalBalani: Sure, that is correct. The question, my answer (and the above comment) were written for Swift, and in Swift you can compare NSObjects with ==. (The pointer comparison from Objective-C would be the "identical-to" operator === in Swift.) – Martin R Sep 14 '15 at 15:20
  • NSFetchedResultsController delegate notification seems totally broken in Xcode7 GM/iOS9 GM/Swift 2.0. The fact, that I receive two delegate notifications (first an update, second a move) if the updated property is part of the sort-descriptor, could be argued as legit. But why the heck do I get an update-notification followed by a insert-notification on my top-level entity if I create and set a relation object?! I even get an insert-notification if I delete an associated entity. – Christian Sep 16 '15 at 08:45
  • A Move change with the same source and destination index is not necessarily a bug, since the objects' indexes can shift due to insertions/deletions at smaller indexes. See my comment in https://github.com/vlas-voloshin/FRCUpdateBugDemo/commit/227e0ad63eaf2241cc3bc274ce2aa76d84a92688#commitcomment-13404406 for a real-world example where omitting the move results in a correctness issue. – numist Sep 24 '15 at 00:26
  • @MarkKrenek Dude!!!! i didn't pay attention great point It Solved my problem :D , i have ios 9 sdk but reproduced the bug on an ios 8.4.1 – dave Oct 21 '15 at 14:37
  • @MartinR - this fix solved a nightmare issue I couldnt fix. I was using moveItems which was causing error on attempt to move and insert at same index. Thanks a lot! – some_id Mar 31 '17 at 13:04
  • @DuncanBabbage: Re your edit: Swift does not require parentheses around the condition in an if statement. – Martin R Jun 02 '17 at 07:58
  • Ah sorry, I inserted your fix into some Objective C code and when Xcode threw up the complaint, without thinking that through I transposed it back here. :) – Duncan Babbage Jun 02 '17 at 08:13
  • On iOS 13.2, the method is not called twice, but this creates another problem: If the cell item is renamed and the resulting item sorted to a new position, then only the .move call happens, but the .update call is missing. So I end up with the renamed item in the right row, but its content is not updated. – LPG Feb 26 '20 at 08:28
20

Update: the described problem occurs only on iOS 8 when building against iOS 9.0 or iOS 9.1 (beta) SDK.

I came up with some horrible workaround today after playing with Xcode 7 beta 6 (iOS 9.0 beta 5) and it seems that it works.

You cannot use reloadRowsAtIndexPaths because in certain cases it's called too early and may cause inconsistency, instead you should manually update your cell.

I still think that the best option is to simply call reloadData.

I believe you can adapt my code for swift with no efforts, I have objective-c project here.

@property NSMutableIndexSet *deletedSections, *insertedSections;

// ...

- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
    [self.tableView beginUpdates];

    self.deletedSections = [[NSMutableIndexSet alloc] init];
    self.insertedSections = [[NSMutableIndexSet alloc] init];
}

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
    [self.tableView endUpdates];
}

- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id<NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
    NSIndexSet *indexSet = [NSIndexSet indexSetWithIndex:sectionIndex];

    switch(type) {
        case NSFetchedResultsChangeDelete:
            [self.tableView deleteSections:indexSet withRowAnimation:UITableViewRowAnimationAutomatic];
            [self.deletedSections addIndexes:indexSet];
            break;

        case NSFetchedResultsChangeInsert:
            [self.tableView insertSections:indexSet withRowAnimation:UITableViewRowAnimationAutomatic];
            [self.insertedSections addIndexes:indexSet];
            break;

        default:
            break;
    }
}

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
    switch(type) {
        case NSFetchedResultsChangeDelete:
            [self.tableView deleteRowsAtIndexPaths:@[ indexPath ] withRowAnimation:UITableViewRowAnimationAutomatic];
            break;

        case NSFetchedResultsChangeInsert:
            [self.tableView insertRowsAtIndexPaths:@[ newIndexPath ] withRowAnimation:UITableViewRowAnimationAutomatic];
            break;

        case NSFetchedResultsChangeMove:
            // iOS 9.0b5 sends the same index path twice instead of delete
            if(![indexPath isEqual:newIndexPath]) {
                [self.tableView deleteRowsAtIndexPaths:@[ indexPath ] withRowAnimation:UITableViewRowAnimationAutomatic];
                [self.tableView insertRowsAtIndexPaths:@[ newIndexPath ] withRowAnimation:UITableViewRowAnimationAutomatic];
            }
            else if([self.insertedSections containsIndex:indexPath.section]) {
                // iOS 9.0b5 bug: Moving first item from section 0 (which becomes section 1 later) to section 0
                // Really the only way is to delete and insert the same index path...
                [self.tableView deleteRowsAtIndexPaths:@[ indexPath ] withRowAnimation:UITableViewRowAnimationAutomatic];
                [self.tableView insertRowsAtIndexPaths:@[ indexPath ] withRowAnimation:UITableViewRowAnimationAutomatic];
            }
            else if([self.deletedSections containsIndex:indexPath.section]) {
                // iOS 9.0b5 bug: same index path reported after section was removed
                // we can ignore item deletion here because the whole section was removed anyway
                [self.tableView insertRowsAtIndexPaths:@[ indexPath ] withRowAnimation:UITableViewRowAnimationAutomatic];
            }

            break;

        case NSFetchedResultsChangeUpdate:
            // On iOS 9.0b5 NSFetchedResultsController may not even contain such indexPath anymore
            // when removing last item from section.
            if(![self.deletedSections containsIndex:indexPath.section] && ![self.insertedSections containsIndex:indexPath.section]) {
                // iOS 9.0b5 sends update before delete therefore we cannot use reload
                // this will never work correctly but at least no crash. 
                UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
                [self _configureCell:cell forRowAtIndexPath:indexPath];
            }

            break;
    }
}

Xcode 7 / iOS 9.0 only

In Xcode 7 / iOS 9.0 NSFetchedResultsChangeMove is still being sent instead of "update".

As a simple workaround, just disable animations for that case:

- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
    UITableViewRowAnimation animation = UITableViewRowAnimationAutomatic;

    switch(type) {

        case NSFetchedResultsChangeMove:
            // @MARK: iOS 9.0 bug. Move sent instead of update. indexPath = newIndexPath.
            if([indexPath isEqual:newIndexPath]) {
                animation = UITableViewRowAnimationNone;
            }

            [self.tableView deleteRowsAtIndexPaths:@[ indexPath ] withRowAnimation:animation];
            [self.tableView insertRowsAtIndexPaths:@[ newIndexPath ] withRowAnimation:animation];

            break;

        // ...
    }
}
pronebird
  • 12,068
  • 5
  • 54
  • 82
  • Why is this answer not getting enough attention? This workaround describes exactly how the problem behaves. +1 – John Estropia Sep 10 '15 at 07:32
  • 2
    We can still reproduce this bug with the XCode 7 GM – John Estropia Sep 11 '15 at 00:30
  • To be fair I don't receive any update events on Xcode GM. I get move event with the same index for both old and new index path. – pronebird Sep 11 '15 at 07:05
  • I'm still having the problem under the Xcode 7 GM seed - it was the first thing i tried out when they dropped it. :( – SimonTheDiver Sep 11 '15 at 10:04
  • I can't believe that but it does not crash in simulator for me, however after playing around with it on iOS 8 running device, same crash happens. – pronebird Sep 13 '15 at 13:51
  • Did you guys file radar? – pronebird Sep 14 '15 at 13:49
  • 2
    Curiously I'm only seeing this on iOS 8.x devices/simulator after compiling with Xcode 7 GM seed. iOS 9 simulator no longer has the issue for me… – Arkku Sep 16 '15 at 11:12
  • 1
    @Andy I converted your code to Swift and shared it as a gist (with credits to you of course): https://gist.github.com/JohnEstropia/d7b25c11ba15564f0b16 – John Estropia Sep 18 '15 at 02:10
  • @Andy thanks for this! My friend swears he saw this happening before iOS9 and XCode 7, but it's exactly what I'm seeing, and replacing the Update with reloadData fixes it completely. The issue seemed to start when I saved new data on a child context in a separate thread. When the table updated, it would throw that error. Thanks for saving me hours and hours of work. – Brian Sep 18 '15 at 03:08
  • @Brian is that still the case with Xcode 7 released 2 days ago? I believe this bug is gone. I am gonna test later today on iOS 8 device. Make sure you don't test on pre-release simulators which remain in system even after upgrade. – pronebird Sep 18 '15 at 08:48
  • I installed XCode7 yesterday, so it's as up to date as it can be I believe. I've been testing only on physical devices as well. Perhaps the bug I'm encountering isn't the same bug, but it presents the same, and the fix is the same - I figure if it looks like a duck and quacks like a duck... – Brian Sep 18 '15 at 08:51
  • 1
    @Brian haha! Looking at it in simulator for iOS 8.4. FRC is totally broken. – pronebird Sep 18 '15 at 08:58
  • The bug where .Move and .Update were being posted for the same object was introduced in the iOS 9 betas and fixed in iOS 9 GM. However, there was also a correctness bug in iOS 8 that was fixed in iOS 9 where some .Move changes were being incorrectly posted as .Update (which caused an exception to be thrown by UIKit when the .Move had the bad luck to share an index with a .Delete or .Insert). The fix is only enabled for apps built against the iOS 9 SDK, which means apps running on iOS 8 still have to deal with the earlier behaviour. – numist Sep 23 '15 at 23:55
  • 1
    Still getting "CoreData: error: Serious application error. An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:. attempt to delete and reload the same index path ( {length = 2, path = 0 - 51}) with userInfo (null)" on iOS8.x devices built against iOS9 in Xcode 7.0. This fix works beautifully. So far I haven't seemed to need last case NSFetchedResultsChangeUpdate change. Upvote, upvote, upvote @Andy – Brett Sep 25 '15 at 06:59
  • Xcode 7.0.1 seems to have borked iOS 9 as well... We've observed `.Move` changes with `indexPath == newIndexPath` on iOS 9 devices now too. – John Estropia Sep 30 '15 at 02:50
  • @JohnEstropia - your solution slightly helps. I've got a big dating service with ,messaging, cashing lots of collections and stuff migrated to swift 2.0 ( it's like facebook, but with dating ) . Would apple fix this? – Nikita Semenov Oct 08 '15 at 12:23
  • 3
    Still experiencing this issue in iOS 8 when building against the latest iOS 9 SDK. The workaround is so painful (even worse when pairing an FRC with a UICollectionView) I'm almost inclined to do an OS check and just reloadData for iOS 8, reserving insert/update/move/delete animations for iOS 9 only – kball Oct 10 '15 at 21:59
  • According to the response that I received from Apple, this is not going to be fixed. @JohnEstropia feel free to edit my answer and add your swift solution. – pronebird Oct 27 '15 at 10:10
  • @Andy Can we ask for Apple's exact wording and the context of your discussion with them? It's kind of hard to accept that this won't be fixed as FRC's are totally broken without any kind of workaround. – John Estropia Oct 27 '15 at 10:42
  • @Andy Thanks! That sounds irresponsible of them though :( – John Estropia Oct 27 '15 at 14:29
16

With regard to this happening on iOS8, with builds compiled against iOS9, on top of the indexPath==newIndexPath problem addressed by some other answers, something else happens which is very weird.

The NSFetchedResultsChangeType enum has four possible values (comments with values are mine):

public enum NSFetchedResultsChangeType : UInt {
    case Insert // 1
    case Delete // 2
    case Move   // 3
    case Update // 4
}

.. however, the controller:didChangeObject:atIndexPath:forChangeType function is sometimes called with an invalid value 0x0.

Swift seems to default to the first switch case at that point so if you have the following structure:

func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
        switch type {
            case .Insert: tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
            case .Delete: tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
            case .Update: tableView.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.None)
            case .Move: tableView.moveRowAtIndexPath(ip, toIndexPath: nip)
        }
    }

.. the invalid call will result in an Insert, and you will get an error like the following:

Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (7) must be equal to the number of rows contained in that section before the update (7), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted)

Simply swapping the cases so that the first case is a rather innocuous Update fixes the problem:

func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
        switch type {
            case .Update: tableView.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.None)
            case .Insert: tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
            case .Delete: tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
            case .Move: tableView.moveRowAtIndexPath(ip, toIndexPath: nip)
        }
    }

Another option would be checking type.rawValue for an invalid value.

Note: while this addresses a slightly different error message than the one posted by the OP, the issue is related; it's somewhat likely that as soon as you fix the indexPath==newIndexPath problem, this one will pop up. Also, the above code blocks are simplified to illustrate the sequence; the appropriate guard blocks are missing, for instance - please don't use them as is.

Credits: this was originally discovered by iCN7, source: Apple Developer Forums — iOS 9 CoreData NSFetchedResultsController update causes blank rows in UICollectionView/UITableView

magma
  • 8,432
  • 1
  • 35
  • 33
  • 1
    I've just implemented the `indexPath==newIndexPath` fix. Since I'm using Objective-C here I didn't encounter this as a _problem_, however I am still seeing a `NSFetchedResultsChangeType` with value `0x0`. The `switch` has no matching `case` so no issues, but this is still something to watch for. – Adam S Oct 06 '15 at 20:14
  • 3
    @AdamS I'm appalled that Swift executes a `case` that _doesn't_ match the provided `switch` value. – magma Oct 06 '15 at 20:23
  • Agreed, that behaviour is concerning. The compiler has to enforce that the switch/case is exhaustive - in the case that it isn't (and can't be caught at compile time, as here), that should be a runtime error or at the very least it shouldn't execute any of the cases! – Adam S Oct 06 '15 at 20:39
9

For some reason NSFetchedResultsController calls .Update followed by .Move after controllerWillChangeContent: is called.

Simply it looks like this: BEGIN UPDATES -> UPDATE -> MOVE -> END UPDATES.

Happens only under iOS 8.x

During one session of update the same cell is reloaded and deleted what cause a crash.

THE SIMPLEST FIX EVER:

The following part of code:

case .Update:
    if let indexPath = indexPath {
        tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
    }

replace with:

case .Update:
    if let indexPath = indexPath {

        // 1. get your cell
        // 2. get object related to your cell from fetched results controller
        // 3. update your cell using that object

        //EXAMPLE:
        if let cell = tableView.cellForRowAtIndexPath(indexPath) as? WLTableViewCell { //1
            let wishlist = fetchedResultsController.objectAtIndexPath(indexPath) as! WLWishlist //2
            cell.configureCellWithWishlist(wishlist) //3
        }
    }

THAT REALLY WORKS.

Bartłomiej Semańczyk
  • 59,234
  • 49
  • 233
  • 358
  • I think I read that reloadRowsAtIndexPaths is actually the better solution in general - is this replacement still necessary in Xcode 9? Or can we safely use reloadRowsAtIndexPaths here? – SAHM Jun 14 '18 at 03:02
  • Here is the article I was referring to: https://oleb.net/blog/2013/02/nsfetchedresultscontroller-documentation-bug/ – SAHM Jun 14 '18 at 03:02
2

The other answers were close for me, but I was receiving "< invalid > (0x0)" as the NSFetchedResultsChangeType. I noticed that it was being interpreted as an "insert" change. So the following fix worked for me:

func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
switch type {
case .Insert:
  // iOS 9 / Swift 2.0 BUG with running 8.4
  if indexPath == nil {
    self.tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
  }
  (etc...)
}

Since every "insert" only comes back with a newIndexPath and no indexPath (and this strange extra insert delegate call is coming back with the same path listed for both newIndexPath and indexPath), this just checks that it's the right kind of "insert" and skips the others.

Brandon Roberts
  • 149
  • 2
  • 3
0

The problem happened because of reload and delete the same indexPath(which a bug produced by apple),so I change the way I handle the NSFetchedResultsChangeUpdate message.

Instead of:

 [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];

I updated the content of the cell manually:

MyChatCell *cell = (MyChatCell *)[self.tableView cellForRowAtIndexPath:indexPath];
CoreDataObject *cdo = [[self fetchedResultsController] objectAtIndexPath:indexPath];
// update the cell with the content: cdo
[cell updateContent:cdo];

It turns out to be working well.

BTW: the update of CoreData object would produce a delete and a insert message.To update the cell content correctly,when the indexPath is equal to the newIndexPath(both the section and row are equal),I reload the cell by
[self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];

Here is the sample code:

- (void)controller:(NSFetchedResultsController *)controller
   didChangeObject:(id)anObject
       atIndexPath:(NSIndexPath *)indexPath
     forChangeType:(NSFetchedResultsChangeType)type
      newIndexPath:(NSIndexPath *)newIndexPath
{
    if (![self isViewLoaded]) return;
    switch(type)
    {
        case NSFetchedResultsChangeInsert:
            [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                              withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeDelete:
            [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                              withRowAnimation:UITableViewRowAnimationFade];
            break;

        case NSFetchedResultsChangeUpdate:{
            MyChatCell *cell = (MyChatCell *)[self.tableView cellForRowAtIndexPath:indexPath];
            CoreDataObject *cdo = [[self fetchedResultsController] objectAtIndexPath:indexPath];
            // update the cell with the content: cdo
            [cell updateContent:cdo];
        }
            break;

        case NSFetchedResultsChangeMove:
            if (indexPath.row!=newIndexPath.row || indexPath.section!=newIndexPath.section){
                [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]
                               withRowAnimation:UITableViewRowAnimationFade];
                [self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath]
                               withRowAnimation:UITableViewRowAnimationFade];
            }else{
                [self.tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
            }

    }
}

I putted the sample code above to gist: https://gist.github.com/dreamolight/157266c615d4a226e772

Stan
  • 142
  • 4