0

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.

vadian
  • 274,689
  • 30
  • 353
  • 361
Ajoy Kumar
  • 21
  • 5
  • Probably not related, but `fetchedDataController` is supposed to be non-optional: `lazy var fetchedDataController: NSFetchedResultsController = {` `let req = NSFetchRequest(entityName: "Product")` and delete the optional type cast `as? NSFetchedResultsController`. You **do** create the controller with a distinct type, you **do** know that it will never be `nil`. – vadian May 30 '17 at 19:56
  • @vadian typecasting the FRC to entity type is required otherwise compiler complains.. – Ajoy Kumar May 31 '17 at 09:14
  • Not if you specify the type in declare line: `NSFetchedResultsController` rather than `NSFetchedResultsController` – vadian May 31 '17 at 09:48
  • @vadian Yes I can do that.. thanks...still I am stuck with my core issue – Ajoy Kumar Jun 01 '17 at 22:37

2 Answers2

1

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)

}

Ajoy Kumar
  • 21
  • 5
0

A fetchedResultsController monitors a particular context - not the sql file. If you have more than one contexts in the app, then changes from one may not be merged into the other. So even if the sql files is updated the context will not be. When you create the context make sure to set the viewContext's automaticallyMergesChangesFromParent = true.

Jon Rose
  • 8,373
  • 1
  • 30
  • 36