Search code examples
iosswiftinheritancecore-datansfetchedresultscontroller

How do I use NSFetchedResultsControllerDelegate in multiple ViewController without repeating the controller code?


I am making an app that uses Core Data, it happens that in this app I have three tableViews populated with different data and all of them are in different controllers, to avoid repeating the code (since it is exactly the same) I would like to have a solution for this.

I have tried through protocols and with a MainController that inherits from UIViewController, which in turn had the extension of NSFetchedResultsControllerDelegate. Im almost positive that doing a super class where I can put this code is the solution, but since I'm new to swift and to programming and I'm also positive that there is something that im not doing as it was supposed.

In the parent class I've made something like this:

class MainController: UIViewController, NSFetchedResultsController {

     var activeTableView: UITableView?
}

And in the child:

class SecdController: MainController {

   @IBOutlet weak var tableView: UITableView!

   override var activeTableView: UITableView? {
     get {return tableView}
     set {}
  }
}

Below is the code I want to be called three controllers.

extension SomeController: NSFetchedResultsControllerDelegate {

    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {

        switch type {

        case .insert:
            tableView.insertRows(at: [newIndexPath!], with: .top)
            break
        case .delete:
            tableView.deleteRows(at: [indexPath!], with: .fade)
            break
        case .update:
            tableView.reloadRows(at: [indexPath!], with: .top)
        case .move:
            tableView.reloadRows(at: [indexPath!], with: .top)
        default:
            fatalError("feature not yet implemented")
        }
    }

    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {

        let indexSet = IndexSet(integer: sectionIndex)

        switch type {

        case .insert:
            tableView.insertSections(indexSet, with: .top)
        case .delete:
            tableView.deleteSections(indexSet, with: .top)
        case .update, .move:
            fatalError("Invalid change.")
        default:
            fatalError("feature not yet implemented")
        }
    }

    func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {

        tableView.beginUpdates()
    }
    func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {

        tableView.endUpdates()
    }
}

Thanks in advance :)


Solution

  • Your MainController should handle NSFetchedResultsControllerDelegate instead of inherit NSFetchedResultsController

    
    class MainController: UIViewController {
    
        @IBOutlet weak var tableView: UITableView!
        var fetchedResultsController: NSFetchedResultsController<NSManagedObject>?
    
    override func viewDidLoad() {
            super.viewDidLoad()
    
            setupFetchedResultsController()
    
           fetchedResultsController?.delegate = self
           // perform fetch
        }
    
    func setupFetchedResultsController() {
            preconditionFailure("The method must be overridden and initialize the fetch result controller.")
        }
    }
    
    extension MainController: NSFetchedResultsControllerDelegate {
    
        func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
    
    // Do not force unwrap
            switch type {
    
            case .insert:
                tableView.insertRows(at: [newIndexPath!], with: .top)
                break
            case .delete:
                tableView.deleteRows(at: [indexPath!], with: .fade)
                break
            case .update:
                tableView.reloadRows(at: [indexPath!], with: .top)
            case .move:
                tableView.reloadRows(at: [indexPath!], with: .top)
            default:
                fatalError("feature not yet implemented")
            }
        }
    
        func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
    
            let indexSet = IndexSet(integer: sectionIndex)
    
            switch type {
    
            case .insert:
                tableView.insertSections(indexSet, with: .top)
            case .delete:
                tableView.deleteSections(indexSet, with: .top)
            case .update, .move:
                fatalError("Invalid change.")
            default:
                fatalError("feature not yet implemented")
            }
        }
    
        func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
    
            tableView.beginUpdates()
        }
        func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
    
            tableView.endUpdates()
        }
    }
    
    

    And then in the other view controller just override setupFetchedResultsController() and setup the fetch results controller:

    class SecondController: MainController {
    
       override func createFetchResultController() {
    
            let fetchRequest: NSFetchRequest<...> = ...
            let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)
            fetchRequest.sortDescriptors = [sortDescriptor]
    
            fetchedResultsController = ...
        }
    }