iOS 9 CoreData NSFetchedResultsController update causes blank rows in UICollectionView/UITableView

Description:

I have separate classes that run NSFetchedResultsController on both UITableView and UICollectionView. They've been working in iOS 8. Since iOS 9, I occasionally get the console error below on NSManagedObject updates. The changes to NSManagedObject is valid and saves properly to persistent store. However, the UI for UITableView/UICollectionView backed by the NSFetchedResultsController breaks and shows blank rows.


Console:

Assertion failure in -[UICollectionView _endItemAnimationsWithInvalidationContext:tentativelyForReordering:]

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 (<NSIndexPath: 0xc000000000400016> {length = 2, path = 0 - 2}) with userInfo (null)


Update:

iOS9 Beta 2 not fixed and no solution yet, but below's the line that's causing the trouble:


self.tableView.reloadRowsAtIndexPaths()


Seems like it's having trouble reloading cells on updates.


Update 2:

iOS9 Beta 3 still broken.

The root cause of all this is during the NSFetchedResultsController's delegate call for an update, NSFetchedResultsChangeMove and NSFetchedResultsChangeUpdate are both called. Not sure if this use to happen in iOS 8 and prior but just didn't throw off errors.


Update 3:

Issue fixed once updated to iOS 9 Beta 5

Replies

You posted that the issues was fixed in beta 5, what was your configuration like? Mine was fix when compiled with beta 5 and run on iOS 9. However, running it on iOS 8.4 still producing the same issues.


Any update on this?

code version: Xcode 7 beta 6

Deployment Target: iOS 8.4

Device OS: iOS 9 beta 3


Work for me on simulator and device

Unfortunately, XCode 7 GM still has NSFetchedResultsController fire NSFetchedResultsChangeTypeMove notifications with equal source and destination NSIndexPaths...

( rdar://22380191 )

When managed object changes

iOS 8 SDK would give me two events via NSFetchedResultsController - update and move (with same from/to indexpath)

iOS9 GM gives me only move (with same from/to indexpath)


I guess for iOS 9 I would have to react to move (with same from/to) as I would for update, otherwise there's no way to reflect changes on that changed managed object ((

i've been struggling with this too. unfortunately - and i'm sure i'm not he only one who filed radars on this, the bug is still in the Xcode 7 GM seed released on sept 9 2015


Running the same code works on Xcode 6 / iOS 8.4 and Xcode 7 / iOS 9 but causes bugs when running on Xcode 7 /iOS 8.4 .

I found a solution that works for me. If you have .Insert case before .Update in your controller:didChangeObject, try moving .Update up so that its the first case option:


Before:

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: break
        }
    }


After:

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: break
        }
    }


I can now compile using Xcode 7 on my iOS 8.4 device and everything works as expected.

This helped me track down the issue!

The delegatecallback is getting called twice for the same index path

The problem is the type is invalid (0x0) in one call. it seems the switch is defaulting to the first case, so changing the order simple updates twice instead of inserting wrongly.


This is compiling using the ios9 (GM) SDK but running on 8.4.


Please fix this apple!

This doesn't do anything for me.

And theoretically, I can't find how changing the "case" order will do anything at all.


I don't know the edge cases here but the best workaround that I've seen so far that works with multiple sections is this:

http://stackoverflow.com/a/32466951/809614

I have encountered the same problem and have begun a sample project for experimentation and providing with a bug report here: https://github.com/friendsoftheweb/Tables I have not been able to fully understand the problem yet or replicate (in controlled circumstances) the problem I'm experiencing, so I have not yet filed a bug report. However, if it may be a useful starting point to experiment for others with the same problem.


My problem specifically is that application which has no issues when building with Xcode 6.4 and run on any tested iOS 8.x or 9.0 is consistently broken when compiled with Xcode 9 gsm seed (7A218) and run on iOS 8.4. Other versions, iOS 8.1, 8.2, 8.3, and 9.0 all work fine. Whenver changes are made to the data model, I receive the same assertion failure warnign metioned in the original post and the table view is brought to broken, unusable, state.


For now, continuing to build with Xcode 6.4 seems to avoid the problem.

NSFetchedResultsController bug is still there in Xcode 7 App Store Release. In the case if you hope for a miracle. As discussed above, all works fine if you build your project with Xcode 6.4.

Since this bug still occurs with XCode 7's public release, I posted a gist of the workaround we have used that worked well for us:

https://gist.github.com/JohnEstropia/d7b25c11ba15564f0b16

(Credits not mine, I just converted the code shared here to Swift and added annotations about what's going on)


I incorporated this workaround in my Core Data library CoreStore which we use in several production apps, and we haven't found any problems so far. Hope this helps you too without having to downgrade to XCode 6.4.

Thanks iCN7!

Changing case order did the trick for me. This bug has been driving me mad.


S.E.

I'm getting the attempt to delete and reload the same index path assertion error in iOS 8.4 (real device and simulator) when handling NSFetchedResultsChangeMove due to rows being reordered. The old and new index paths are different, but I am seeing an update action before the move as described above. This just started happening with Xcode 7. iOS 9 seems to work fine.

I am also having this problem with an app compiled using Xcode 7 with iOS 7 as the deployment target. It only occurs on devices running iOS 8.3 and 8.4: ones with iOS 8.2 or 9.0 are fine. This is both on physical devices and in the Simulator.

any idea, how this assertion could be bypassed?