Last active
May 7, 2020 09:39
-
-
Save tonnylitao/314fe120ceaf702c0aa9 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
//some time, frc section may be need section offset | |
@objc public protocol MFetchedResultsControllerOffsetSectionDelegate{ | |
func offsetSection() -> Int | |
} | |
class MFetchedResultsController: NSFetchedResultsController, NSFetchedResultsControllerDelegate { | |
weak var viewController: UIViewController? //UITableViewController UICollectionViewController | |
weak var scrollView: UIScrollView? //TableView CollectionView | |
weak var offsetSectionDelegate: MFetchedResultsControllerOffsetSectionDelegate? | |
private var offsetSection: Int! | |
var deletedSectionIndexes: NSMutableIndexSet! | |
var insertedSectionIndexes: NSMutableIndexSet! | |
var deletedRowIndexPaths: [NSIndexPath]! | |
var insertedRowIndexPaths: [NSIndexPath]! | |
var updatedRowIndexPaths: [NSIndexPath]! | |
//if frc is alloc after viewWillAppear, you should set it to false menually | |
var isViewInvisble: Bool = true | |
//in viewWillAppear call | |
func startListening(){ | |
//for first time viewWillAppear, unnecessary performFetch and reloadData | |
if isViewInvisble { | |
isViewInvisble = false | |
return | |
} | |
if let _ = viewController { | |
self.delegate = self | |
do { | |
try self.performFetch() | |
if let t = scrollView as? UITableView { | |
t.reloadData() | |
}else if let c = scrollView as? UICollectionView { | |
c.reloadData() | |
} | |
} catch (_){ | |
} | |
} | |
} | |
//in viewWillDisappear call | |
func endListening(){ | |
self.delegate = nil | |
} | |
// MARK: NSFetchedResultsControllerDelegate | |
func controllerWillChangeContent(controller: NSFetchedResultsController){ | |
assert(viewController != nil, "viewController is nil") | |
assert(scrollView != nil, "scrollView is nil") | |
if viewController == nil || scrollView == nil || DataEnvironment.sharedDataEnvironment().notNeedUpdateVC == viewController || !viewController!.isViewLoaded() { | |
return | |
} | |
print("FRC VC:", viewController); | |
if let d = self.offsetSectionDelegate { | |
offsetSection = d.offsetSection() | |
}else{ | |
offsetSection = 0 | |
} | |
insertedSectionIndexes = NSMutableIndexSet() | |
deletedSectionIndexes = NSMutableIndexSet() | |
insertedRowIndexPaths = [NSIndexPath]() | |
deletedRowIndexPaths = [NSIndexPath]() | |
updatedRowIndexPaths = [NSIndexPath]() | |
} | |
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType){ | |
if viewController == nil || scrollView == nil || DataEnvironment.sharedDataEnvironment().notNeedUpdateVC == viewController || !viewController!.isViewLoaded() { | |
return | |
} | |
switch type { | |
case .Insert: | |
self.insertedSectionIndexes.addIndex(sectionIndex+offsetSection) | |
case .Delete: | |
self.deletedSectionIndexes.addIndex(sectionIndex+offsetSection) | |
default: break | |
} | |
} | |
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?){ | |
if viewController == nil || scrollView == nil || DataEnvironment.sharedDataEnvironment().notNeedUpdateVC == viewController || !viewController!.isViewLoaded() { | |
return | |
} | |
print(type.rawValue, indexPath, newIndexPath) | |
if let o = anObject as? NSManagedObject { | |
print(o.entity.name) | |
} | |
switch type { | |
case .Insert: | |
if indexPath == nil { //fix iOS8 bug | |
let section = newIndexPath!.section+offsetSection | |
//avoid insert row again if section inserted | |
if !self.insertedSectionIndexes.containsIndex(section) { | |
self.insertedRowIndexPaths!.append(NSIndexPath(forRow: newIndexPath!.row, inSection: newIndexPath!.section+offsetSection)) | |
} | |
} | |
case .Delete: | |
let section = indexPath!.section+offsetSection | |
//avoid delete row again if section deleted | |
if !self.deletedSectionIndexes.containsIndex(section) { | |
self.deletedRowIndexPaths.append(NSIndexPath(forRow: indexPath!.row, inSection: indexPath!.section+offsetSection)) | |
} | |
case .Move: | |
if let i = indexPath, nI = newIndexPath { | |
if i.row != nI.row || i.section != nI.section { //fix iOS9 bug | |
if !self.deletedSectionIndexes.containsIndex(i.section) { | |
self.deletedRowIndexPaths.append(NSIndexPath(forRow: i.row, inSection: i.section+offsetSection)) | |
} | |
if !self.insertedSectionIndexes.containsIndex(nI.section){ | |
self.insertedRowIndexPaths.append(NSIndexPath(forRow: nI.row, inSection: nI.section+offsetSection)) | |
}else{ | |
assert(false, "there is another bug") | |
} | |
} | |
} | |
case .Update: | |
let ip = NSIndexPath(forRow: indexPath!.row, inSection: indexPath!.section+offsetSection) | |
self.updatedRowIndexPaths.append(ip) | |
} | |
} | |
func controllerDidChangeContent(controller: NSFetchedResultsController){ | |
defer { | |
//clean | |
self.deletedSectionIndexes = nil | |
self.insertedSectionIndexes = nil | |
self.deletedRowIndexPaths = nil | |
self.insertedRowIndexPaths = nil | |
self.updatedRowIndexPaths = nil | |
} | |
if viewController == nil || scrollView == nil || DataEnvironment.sharedDataEnvironment().notNeedUpdateVC == viewController || !viewController!.isViewLoaded() { | |
return | |
} | |
print("insert section: ", self.insertedSectionIndexes) | |
print("delete section: ", self.deletedSectionIndexes) | |
print("insert indpath: ", self.insertedRowIndexPaths) | |
print("delete indpath: ", self.deletedRowIndexPaths) | |
print("update indpath: ", self.updatedRowIndexPaths) | |
//fix iOS8 bug: update and then move same indexPath | |
self.updatedRowIndexPaths = self.updatedRowIndexPaths.filter { | |
return !self.insertedSectionIndexes.contains($0.section) && | |
!self.deletedSectionIndexes.contains($0.section) && | |
!self.insertedRowIndexPaths.contains($0) && | |
!self.deletedRowIndexPaths.contains($0) | |
} | |
print("update indpath: ", self.updatedRowIndexPaths) | |
let c = self.insertedSectionIndexes.count + | |
self.deletedSectionIndexes.count + | |
self.insertedRowIndexPaths.count + | |
self.deletedRowIndexPaths.count + | |
self.updatedRowIndexPaths.count | |
if c > 0 { //fix iOS8/9 bug: count maybe 0 | |
if let tableView = scrollView as? UITableView { | |
tableView.beginUpdates() | |
//for section | |
if self.deletedSectionIndexes.count > 0 { | |
tableView.deleteSections(self.deletedSectionIndexes, withRowAnimation: UITableViewRowAnimation.Automatic) | |
} | |
if self.insertedSectionIndexes.count > 0 { | |
tableView.insertSections(self.insertedSectionIndexes, withRowAnimation: UITableViewRowAnimation.Automatic) | |
} | |
//for row | |
if self.updatedRowIndexPaths.count > 0 { //put update first | |
tableView.reloadRowsAtIndexPaths(self.updatedRowIndexPaths, withRowAnimation: UITableViewRowAnimation.None) | |
} | |
if self.deletedRowIndexPaths.count > 0 { | |
tableView.deleteRowsAtIndexPaths(self.deletedRowIndexPaths, withRowAnimation: UITableViewRowAnimation.Automatic) | |
} | |
if self.insertedRowIndexPaths.count > 0 { | |
tableView.insertRowsAtIndexPaths(self.insertedRowIndexPaths, withRowAnimation: UITableViewRowAnimation.Automatic) | |
} | |
tableView.endUpdates() | |
}else if let collectionView = scrollView as? UICollectionView { | |
collectionView.performBatchUpdates({ () -> Void in | |
if self.deletedSectionIndexes.count > 0 { | |
collectionView.deleteSections(self.deletedSectionIndexes) | |
} | |
if self.insertedSectionIndexes.count > 0 { | |
collectionView.insertSections(self.insertedSectionIndexes) | |
} | |
if self.deletedRowIndexPaths.count > 0 { | |
collectionView.deleteItemsAtIndexPaths(self.deletedRowIndexPaths) | |
} | |
if self.insertedRowIndexPaths.count > 0 { | |
collectionView.insertItemsAtIndexPaths(self.insertedRowIndexPaths) | |
} | |
if self.updatedRowIndexPaths.count > 0 { | |
collectionView.reloadItemsAtIndexPaths(self.updatedRowIndexPaths) | |
} | |
}, completion: { (_) -> Void in | |
}) | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Wow, thank you so much for this! Very interestingly, you seem to have the exact same use case as me - I also need offsetSection since I combine two FRC delegates in one tableview.
Your iOS8/9 fixes are spot on, as far as I can tell - you found more than me. The thing you fixed in line 108 frustrated me today, and I couldn't find out why that was happening. So.. thanks!
I'm happy to report that your code also works with Obj-C with minor adjustments (XCode's Swift header generation is so crappy that I rather not experiment now which of my changes made it Obj-c compatible.. but I made the class
public
and added@objc public
to the scrollView and offsetSectionDelegate vars).