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.