Last active
June 8, 2018 08:00
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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