How does NSFetchedResultsController and its delegate work?

0

As the question correctly says, I want to know how NSFetchedResultsController works and why use delegate methods instead of tableView.reloadData .

I am using them to build a TableView but it is not good at all, when loading the app it does not show me the entities in each cell and when adding a new entity, it is lagged and neither the lines of the cells

I leave the project in dropbox:

link

and a bit of the code below.

class TableViewController : UITableViewController, AddCourseViewControllerDelegate, NSFetchedResultsControllerDelegate {

    var managedObjectContext : NSManagedObjectContext!

    //Este objeto se encarga de integrar un fetchRequest con la ViewController.
    lazy var fetchedResultController : NSFetchedResultsController = {//Es un closure que devuelve un objeto.
        () -> NSFetchedResultsController in

        let fetch = NSFetchRequest()
        let entity = NSEntityDescription.entityForName("Course", inManagedObjectContext: self.managedObjectContext)
        let sort = NSSortDescriptor(key: "author", ascending: true)

        fetch.entity = entity
        fetch.sortDescriptors = [sort]

        let fetchedResult : NSFetchedResultsController = NSFetchedResultsController(fetchRequest: fetch, managedObjectContext: self.managedObjectContext, sectionNameKeyPath: "author", cacheName: nil)

        fetchedResult.delegate = self

        return fetchedResult
    }()

    override func viewDidLoad() {
        super.viewDidLoad()

        do {
            try fetchedResultController.performFetch()
        } catch {
            print("Error")
        }


    }

    func controllerWillChangeContent(controller: NSFetchedResultsController) {
        self.tableView.beginUpdates()
    }

    func controllerDidChangeContent(controller: NSFetchedResultsController) {
        self.tableView.endUpdates()
    }


    //Mark: - NSFetchedResultController delegate methods

    func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
        switch type {
        case .Delete:
            tableView.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: .Fade)
            break;
        case .Insert:
            tableView.insertRowsAtIndexPaths([newIndexPath!], withRowAnimation: .Fade)
                break;
        case .Move:
            tableView.moveRowAtIndexPath(indexPath!, toIndexPath: newIndexPath!)
                break;
        case .Update:
            let curso = controller.objectAtIndexPath(indexPath!) as! Course
            let cell = tableView.cellForRowAtIndexPath(indexPath!)
            cell?.textLabel?.text = curso.title
            cell?.detailTextLabel?.text = curso.author
                break;
        }

    }

    func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {

        switch type {
        case .Insert:
            let set = NSIndexSet(index: sectionIndex)
            tableView.insertSections(set, withRowAnimation: .Fade)
            break;
        case .Delete:
            let set = NSIndexSet(index: sectionIndex)
            tableView.deleteSections(set, withRowAnimation: .Fade)
            break;
        default:
            break;
        }



    }

    //MARK: - TableView dataSource

    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return fetchedResultController.sections!.count
    }

    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return fetchedResultController.sections![section].numberOfObjects
    }

    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("Cell")!

        let course : Course = (fetchedResultController.fetchedObjects![indexPath.row]) as! Course

        cell.textLabel!.text = course.title
        cell.detailTextLabel!.text = course.author

        return cell
    }


    private func getFetchedController() -> NSFetchedResultsController {
        let fetch = NSFetchRequest()
        let entity = NSEntityDescription.entityForName("Course", inManagedObjectContext: self.managedObjectContext)
        let sort = NSSortDescriptor(key: "author", ascending: true)

        fetch.entity = entity
        fetch.sortDescriptors = [sort]

        let fetchedResult = NSFetchedResultsController(fetchRequest: fetch, managedObjectContext: self.managedObjectContext, sectionNameKeyPath: "author", cacheName: nil)

        return fetchedResult
    }


    //MARK: - AddCourseDelegate

    func addCourseViewControllerDidSave() {
        do {
            try managedObjectContext.save()
        } catch {
            print("Error al guardar el curso.")
        }
        self.dismissViewControllerAnimated(true, completion: nil)
    }

    func addCourseViewControllerDidCancel(curso : Course) {
        managedObjectContext.deleteObject(curso)
        self.dismissViewControllerAnimated(true, completion: nil)
    }


    //MARK: - Segue way

    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        if segue.identifier == "addCourseSegue" {
            let nextVc = segue.destinationViewController as! AddCourseViewController
            nextVc.delegate = self

            let selectedCourse = NSEntityDescription.insertNewObjectForEntityForName("Course", inManagedObjectContext: managedObjectContext) as! Course
            nextVc.course = selectedCourse

        }
    }


}
    
asked by MatiEzelQ 20.05.2016 в 19:31
source

1 answer

1

To use a tableview you need 2 delegates (UITableViewDataSource and UITableViewDelegate) so that the controller knows how to make the table, you will have to know how many sections there are, how many rows there are for each section, etc. (tableviewdelegate) and then you will have to ask to someone (datasourcedelegate) what information to put in each cell.

Once you have the data in some way (Core data, SQLite to bravas, an array, or whatever) you have to have the form to be able to provide the table with the necessary information so that it knows the sections, the rows by section and what information goes in each cell. If you use Coredata, it does everything automatically, but if you have another source of data you have to look for life to provide it. Why not use tableView.reloadData () and that's it because this method only tells the controller to re-assemble the table, so it will call the methods of numberOfSections, numberOfRows, etc, so these methods must be implemented if or if. And in principle you should not call the reloadData () method, when you start the driver, it already does it.

The NSFetchResultsController, what it does is consult the DB in SQLite through CoreData and keep managing the data and providing the DataSource methods that the table will need. The table asks and the NSFetchresultcontroller gives you the information. The NSFetchresultsController configures it to do the search in the entity that you tell it, ordered in any way and it takes care of everything else. It deals with managing memory, requesting more data, releasing what is no longer displayed, accessing relationships, etc.

I have looked at the code a bit above, and it does not seem to have "errors", more or less it should work. I advise you to put some breakpoints in the methods of numberOfSectionsInTableView, cellForRowAtIndexPath and numberOfRowsInSection to verify that there you have the correct information that should appear. The same your problem is in the definition of the model, you also do not specify the error that appears, you say it does not work.

    
answered by 21.05.2016 / 09:40
source