Search code examples
iosswiftuitableviewnsfetchedresultscontroller

Using NSFetchedResultsController on a part of UITableView


I have a UITableViewController in a UISplitViewController. This tableViewController receives two things:

  1. Array of Invoices
  2. Customer Object

So what I'm doing is populating the invoice details in the first section (or Section 0) of the UITableView.

For the second part, I am using NSFetchedResultsController to fetch all properties of the customer that have invoice == nil and displaying that in the second section of the table view.

So basically, using NSFetchedResultsController only on a part of UITableViewController and not the entire thing.

Now, everything works fine. But when I tap the row of a section 2, I provide the user to add that property to a new invoice. When I do that, I get this error:

CoreData: error: Serious application error.  An exception was caught from the delegate of NSFetchedResultsController during a call to -controllerDidChangeContent:.  Invalid update: invalid number of rows in section 0.  The number of rows contained in an existing section after the update (2) must be equal to the number of rows contained in that section before the update (1), plus or minus the number of rows inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out). with userInfo (null)

Not sure why that is happening. I understand the fetchedResultsController is expecting Section 0 but I've made sure it is manually forced to use Section 1 only.

The code for reference is:

func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
    switch (type) {
    case .Insert:
        if let indexPath = newIndexPath {
            let editedNewIndexPath = NSIndexPath(forRow: indexPath.row, inSection: 1)
            tableView.insertRowsAtIndexPaths([editedNewIndexPath], withRowAnimation: .Fade)
        }
        break;
    case .Delete:
        if let indexPath = indexPath {
            let editedCurrentIndexPath = NSIndexPath(forRow: indexPath.row, inSection: 1)
            tableView.deleteRowsAtIndexPaths([editedCurrentIndexPath], withRowAnimation: .Fade)
        }
        break;
    case .Update:
        if let indexPath = indexPath {
            let editedCurrentIndexPath = NSIndexPath(forRow: indexPath.row, inSection: 1)
            if let cell = tableView.cellForRowAtIndexPath(editedCurrentIndexPath){
                configureCell(cell, atIndexPath: editedCurrentIndexPath)
                //tableView.selectRowAtIndexPath(indexPath, animated: true, scrollPosition: .None)
            }
        }
        break;
    case .Move:
        if let indexPath = indexPath {
            let editedCurrentIndexPath = NSIndexPath(forRow: indexPath.row, inSection: 1)
            tableView.deleteRowsAtIndexPaths([editedCurrentIndexPath], withRowAnimation: .Fade)
        }

        if let newIndexPath = newIndexPath {
            let editedNewIndexPath = NSIndexPath(forRow: newIndexPath.row, inSection: 1)
            tableView.insertRowsAtIndexPaths([editedNewIndexPath], withRowAnimation: .Fade)
        }
        break;
    }
}

// MARK: - Table view data source

override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return 2
}

override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    if section == 0{
        if let ivData = self.invoices{
            print("Section0 - \(ivData.count)")
            return ivData.count
        }
    }
    else if section == 1{
        if let sections = fetchedResultsController.sections {
            let sectionInfo = sections[0]
            print("Section1 - \(sectionInfo.numberOfObjects)")
            if sectionInfo.numberOfObjects > 0{
                return sectionInfo.numberOfObjects
            }
            else{
                return 1
            }
        }
    }
    return 0
}

The code to save a new invoice is:

do{
                    self.invoices!.append(billRecord as! InvoiceInfo)
                    try billRecord.managedObjectContext?.save()
                    try self.customer!.managedObjectContext?.save()
                    try record.managedObjectContext?.save()
                    //self.lookUpLotsWithoutBills()
                    self.tableView.reloadData()

                }
                catch{
                    self.invoices!.removeObject(ivRecord)
                    let alertController = UIAlertController(title: "Unexpected Error", message: "Your data could not be updated. Please try again later.", preferredStyle: .Alert)
                    alertController.addAction(UIAlertAction(title: "Done", style: .Cancel, handler: nil))
                    self.presentViewController(alertController, animated: true, completion: nil)
                }

What could be wrong here?


Solution

  • It looks like your FRC is working fine, but that your code isn't telling the table view about a new invoice row being added to section 0. It isn't exactly clear why, but I'm going to guess its because the FRC fires an update after you've added the invoice but before you call reload data on the table. At that point in time the row count for section 0 is wrong and you haven't told the table about it.