NSFetchedResultsController major issue

I was working on a app with Xcode 6/iOS 8 for awhile now. The other day iOS 9 beta 4 and a new Xcode 7 beta was released, and I thought it would be stable enough to update my app to use Swift 2 (100% Swift project). The app supports iOS 8 and 9 now. I ran the app to my iOS 8 device, and noticed a strange effect on NSFetchedResultsController on iOS 8 only (doesn't happen on iOS 9 with Swift 2 for some reason).


So here is my implementation of NSFetchedResultsController,

func setupReturningShowsFetchedResultsController() {
let fetchRequest = NSFetchRequest(entityName: "TVShow")
let titleSort = NSSortDescriptor(key: "title", ascending: true)
fetchRequest.sortDescriptors = [titleSort];
let statusPredicate1 = NSPredicate(format: "status == 'returning series'")
let statusPredicate2 = NSPredicate(format: "status == 'in production'")
let statusPredicates = NSCompoundPredicate(orPredicateWithSubpredicates: [statusPredicate1, statusPredicate2])
let predicate = NSPredicate(format: "upcomingEpisode == nil")
fetchRequest.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [statusPredicates, predicate])
fetchRequest.fetchBatchSize = 14;
returningShowsResultsController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: coreDataStack.context, sectionNameKeyPath: nil, cacheName: nil)
do {
try returningShowsResultsController!.performFetch()
}
catch let error as NSError {
print(error)
}
catch {
}
}


The delegate is set to my view controller, but the FRC is set up in a data source class I made.


Anyways, in my app I create only ONE TVShow object, the status is "returning series" and it has no upcomingEpisode, so it is inserted into this FRC. I update 1 property and 2 relationships,

public func updateUnwatchedCount() {
self.unwatchedCount = self.numberOfEpisodesLeftToWatch()
/
if let nextEpisode = self.getNextEpisodeToWatch() {
self.nextEpisodeToWatch = nextEpisode
}
else {
/
self.nextEpisodeToWatch = nil
}
/
if let upcomingEpisode = self.getNextEpisodeToAir() {
self.upcomingEpisode = upcomingEpisode
}
else {
/
self.upcomingEpisode = nil
}
}


I then save the context (on the main thread).

What would you expect the NSFetchedResultsChangeType to be when this NSFetchedResultsControllerDelegate method is called?

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


I expect only the update type, there is only one object and it was being update so that is the logcal assumption. For some reason, on iOS 8 only with Swift 2 and built with Xcode 7 beta 4, there are 2 calls to this delegate method for that 1 update. The first time, the NSFetchedResultsChangeType is .Update, but the second time it is .Insert. The app then throws this error,


2015-07-24 10:04:33.992 TVShows[35366:3921961] *** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit/UIKit-3347.44.2/UITableView.m:1623
Invalid update: invalid number of rows in section 1. The number of rows contained in an existing section after the update (1) must be equal to the number of rows contained in that section before the update (1), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).


When I built the app before with Xcode 6, Swift 1.2 and iOS 8 only the delegate method was only called once as an update, but is now called twice on a iOS 8.4 device, project built with Swift 2 on Xcode 7. I have file a radar (21983293), I hope this bug can be fixed soon, I still need to be able to insert table view cells but the only fix I could find is comment out the code to insert table view cells.

This was causing problems for me, too. As a work around, I realized even though it's sending <invalid> as the type, it's still reading it as an Insert...


So I changed how inserts are happening:


case .Insert:
// iOS 9 / Swift 2.0 BUG with running 8.4
if indexPath == nil {
self.tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
}


This seems to have fixed the problem. Hope this helps!

Thank you for this post. I spent an awful lot of time trying to figure this out. I saw the update/insert behavior and should have focused on that, but I was too busy blaming myself and looking elsewhere :-). The Xcode 7/Swift 2 update has been difficult for my app. :-)

NSFetchedResultsController major issue
 
 
Q