Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
//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
})
}
}
}
}
@manmal

This comment has been minimized.

Copy link

manmal commented Dec 11, 2015

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).

@gcox

This comment has been minimized.

Copy link

gcox commented Nov 3, 2016

@TonnyTao Shouldn't i.section on line 126 and nI.section on line 130 be i.section+offsetSection and nI.section+offsetSection respectively?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.