Skip to content

Instantly share code, notes, and snippets.

@JohnEstropia
Last active June 8, 2018 08:00
Show Gist options
  • Save JohnEstropia/d7b25c11ba15564f0b16 to your computer and use it in GitHub Desktop.
Save JohnEstropia/d7b25c11ba15564f0b16 to your computer and use it in GitHub Desktop.
A workaround for an NSFetchedResultsController bug introduced in XCode 7. Credits to stackoverflow user Andy: http://stackoverflow.com/a/32466951/809614
// Workaround a nasty bug introduced in XCode 7 targeted at iOS 8 devices
// https://forums.developer.apple.com/message/9998#9998
// https://forums.developer.apple.com/message/31849#31849
// This is not my original idea. Please give your stars to stackoverflow user Andy for sharing his solution: http://stackoverflow.com/a/32466951/809614
class MyFetchedResultsControllerDelegate: NSObject, NSFetchedResultsControllerDelegate {
// ... your code
// MARK: Private
// These two Sets let us track changes in the sections so we can use them for analysis later on
private var deletedSections = Set<Int>()
private var insertedSections = Set<Int>()
// MARK: NSFetchedResultsControllerDelegate
@objc dynamic func controllerWillChangeContent(controller: NSFetchedResultsController) {
// Very important to clear these. You can also probably do this in controllerDidChangeContent(_:)
self.deletedSections = []
self.insertedSections = []
// handle normally
}
@objc dynamic func controllerDidChangeContent(controller: NSFetchedResultsController) {
// handle normally here
}
@objc dynamic func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
if #available(iOS 9, *) {
// iOS 9 is immune to this bug. handle normally here
return
}
switch type {
case .Move:
guard let indexPath = indexPath, let newIndexPath = newIndexPath else {
return // This shouldn't happen either way and should be fine to just force-unwrap, but just for safe measure.
}
if indexPath == newIndexPath
&& self.deletedSections.contains(indexPath.section) {
// This is where we get our crash:
// - The indexPath.section was already deleted so we have no business to move it to newIndexPath.section (which our if-statement checked to be equal with itself)
// - What we do then is treat this case as an ".Insert"
// handle as ".Insert" here
return
}
case .Update:
guard let section = indexPath?.section else {
return // This shouldn't happen either way and should be fine to just force-unwrap, but just for safe measure.
}
if self.deletedSections.contains(section)
|| self.insertedSections.contains(section) {
// This indexPath's section was either deleted or inserted, so we have no business updating it.
// Ignore this notification and wait for the upcoming ".Move" notification which we will treat as a ".Insert"
return
}
default: // other cases are fine, just handle normally
break
}
// handle normally here
}
@objc dynamic func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
switch type {
// Very important. We'll use these info for analysis later.
case .Delete: self.deletedSections.insert(sectionIndex)
case .Insert: self.insertedSections.insert(sectionIndex)
default: break
}
// handle normally here
}
@objc dynamic func controller(controller: NSFetchedResultsController, sectionIndexTitleForSectionName sectionName: String) -> String? {
// handle normally here
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment