Search code examples
iosswiftcore-dataazure-mobile-servicesnsfetchedresultscontroller

Updates to core data object from SyncTable pull, not calling NSFeatchedResults delegate methods


I am using NSFetchedResultsController to track and load UITableView with two sections(based on available true or false) using data from coredata entity. Coredata entity is populated by azure syncTable pull method. I call fetchedDataController?.performFetch() from ViewDidLoad() method in VC. Data is divided into two sections - section 1 available is false, section2- avaliable is true in the entity data.

Here is code snippet of NSFRC initialization:

lazy var fetchedDataController: NSFetchedResultsController<Product>? = {

        let req = NSFetchRequest<NSFetchRequestResult>(entityName: "Product")

        req.sortDescriptors = [NSSortDescriptor(key: "available", ascending: true),     NSSortDescriptor(key: "createdAt", ascending: false)]
        guard let dbContext = self.appDelegate?.managedObjectContext else { return nil }

        let controller = NSFetchedResultsController(fetchRequest: req, managedObjectContext: dbContext, sectionNameKeyPath: "available", cacheName: nil) as? NSFetchedResultsController<Product>

        controller?.delegate = self
        return controller
    }() 

Issues: 1. Even there is update in entity row data FRC delegate methods are not fired automatically. Like I changed available from true to false in the backend and did a SyncTable.pull, I can see that coredata table is updated with latest data( I opened the .sql file and able to see the data)

  1. To overcome workaround I called perform fetch every time I do a pull, in this case when I scroll down to section 2 app is crashing with index error, that means it is not updating the table section and rows properly.

Delegate methods implementation:

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

   self.tableView.beginUpdates()
}

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

    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
        let sectionIndexSet = IndexSet(integer: sectionIndex)
        switch type {
            case .insert:
                self.tableView.insertSections(sectionIndexSet, with: .fade)
            case .update:
                self.tableView.reloadSections(sectionIndexSet, with: .fade)
            case .delete:
                self.tableView.deleteSections(sectionIndexSet, with: .fade)
            default: break
        }
    }


    func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
        switch type {
            case .insert:
                if let newIndex = newIndexPath {
                    self.tableView.insertRows(at: [newIndex], with: .fade)
                }
            case .update:
                if let index = indexPath {
                    self.tableView.reloadRows(at: [index], with: .fade)
                }
            case .move:
                if let index = indexPath {
                    self.tableView.deleteRows(at: [index], with: .fade)
                    if let newIndex = newIndexPath {
                        self.tableView.insertRows(at: [newIndex], with: .fade)
                    }
                }
            case .delete:
                if let index = indexPath {
                    self.tableView.deleteRows(at: [index], with: .fade)
                }
            default: break
        }
    }

I am stuck into this any help is appreciated.


Solution

  • Adding the observer for managedobjectcontext save solves this issue:

    NotificationCenter.default.addObserver(self, selector: #selector(refreshContent), name: NSNotification.Name.NSManagedObjectContextDidSave, object: nil)

    func refreshContent(notif: Notification) { self. fetchedDataController?.managedObjectContext.mergeChanges(fromContextDidSave: notifn)

    }