Items are displayed in wrong section in tableView

Hi,


I have the following problem with this code: for a project I display a list of tasks in a tableview. The list is divided in 3 sections: open, in progress, complete (based on the task status). When I now create the tasks with the proper status and go back to the list, the items appear in the correct section. I can add more tasks and the list gets updated properly.

But when I now close the program and start it again, the tasks are displayed in the wrong sections. For example


This is how the table view looks like while I am creating the tasks


  • Open
    • open_1
    • open_2
  • In Progress
    • progress_1
    • progress_2
  • Complete
    • comp_1
    • comp_2


Now I close the program, start it again and look at the list, this is how it shows:


  • Open
    • progress_1
    • progress_2
  • In Progress
    • open_1
    • open_2
  • Complete
    • comp_1
    • comp_2


Here is my code for the controller:


import UIKit
import CoreData
class TaskTableViewController: UITableViewController, NSFetchedResultsControllerDelegate {
  var project: ProjectMO!
  var tasks:[TaskMO] = []
  var fetchResultController: NSFetchedResultsController<TaskMO>!


    override func viewDidLoad() {
        super.viewDidLoad()

  let fetchRequest: NSFetchRequest<TaskMO> = TaskMO.fetchRequest()
  fetchRequest.predicate = NSPredicate(format: "project = %@", self.project)
  let sortDescriptor = NSSortDescriptor(key: "name", ascending: true)

  fetchRequest.sortDescriptors = [sortDescriptor]

  if let appDelegate = (UIApplication.shared.delegate as? AppDelegate) {
  let context = appDelegate.persistentContainer.viewContext
  fetchResultController = NSFetchedResultsController(fetchRequest: fetchRequest, managedObjectContext: context, sectionNameKeyPath: #keyPath(TaskMO.taskStatus), cacheName: nil)
  fetchResultController.delegate = self

  do {
  try fetchResultController.performFetch()
  if let fetchedObjects = fetchResultController.fetchedObjects {
  tasks = fetchedObjects
  }
  } catch {
  print(error)
  }
  }
        /
        / 
        /
        /
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        /
    }
    / 
    override func numberOfSections(in tableView: UITableView) -> Int {
        /
  guard let sections = fetchResultController.sections else { return 0 }
  print("Sections.count: " + String(sections.count))
  return sections.count
    }
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  guard let sectionInfo = fetchResultController.sections?[section] else { fatalError("Unexpected Section") }
  print("Setting section titles: " + sectionInfo.name + "/" + String(sectionInfo.numberOfObjects))
  return sectionInfo.numberOfObjects
    }

  override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
  guard let sectionInfo = fetchResultController.sections?[section] else { fatalError("Unexpected Section") }
  return sectionInfo.name
  }

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

    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)

  let task = fetchResultController.object(at: indexPath)
  print(indexPath)
  cell.textLabel?.text = task.name
        return cell
    }


    /
    /
    override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
        /
        return true
    }
    */
    /
    /
    override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
        if editingStyle == .delete {
            /
            tableView.deleteRows(at: [indexPath], with: .fade)
        } else if editingStyle == .insert {
            /
        }   
    }
    */
    /
    /
    override func tableView(_ tableView: UITableView, moveRowAt fromIndexPath: IndexPath, to: IndexPath) {
    }
    */
    /
    /
    override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
        /
        return true
    }
    */

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

  func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
  print("didChange anObject()")
  switch type {
  case .insert:
  if let newIndexPath = newIndexPath{
  tableView.insertRows(at: [newIndexPath], with: .fade)
  }

  case .delete:
  if let indexPath = indexPath {
  tableView.deleteRows(at: [indexPath], with: .fade)
  }
  default:
  tableView.reloadData()

  }
  }

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

  override func tableView(_ tableView: UITableView, editActionsForRowAt indexPath: IndexPath) -> [UITableViewRowAction]? {
  /
  let deleteAction = UITableViewRowAction(style: UITableViewRowActionStyle.default, title: "Delete",handler: { (action, indexPath) -> Void in

  if let appDelegate = (UIApplication.shared.delegate as? AppDelegate) {
  let context = appDelegate.persistentContainer.viewContext
  let objectToDelete = self.fetchResultController.object(at: indexPath)
  context.delete(objectToDelete)

  appDelegate.saveContext()
  }

  })

  return[deleteAction]
  }
  /
  override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
  if segue.identifier == "addTaskToProject" {
  let destinationController = segue.destination as! AddTaskToProjectTableViewController
  destinationController.project = project
  destinationController.editMode = false
  } else if segue.identifier == "editTaskToProject" {
  if let indexPath = tableView.indexPathForSelectedRow {
  let destinationController = segue.destination as! AddTaskToProjectTableViewController
  destinationController.project = project
  destinationController.task = fetchResultController.object(at: indexPath)
  destinationController.editMode = true
  destinationController.title = "Show task"
  }
  }
  }

  @IBAction func close(segue:UIStoryboardSegue) {
  print("icoming close from task creation close()")

  /
  if project.tasks != nil {
  tasks = project.tasks?.allObjects as! [TaskMO]
  tasks = tasks.sorted(by: { $0.name! < $1.name! })
  print("close() tasks.count: " + String(tasks.count))
  }
  */

  tableView.reloadData()
  }

  @IBAction func unwindFromTask(segue:UIStoryboardSegue){
  print("icoming close from task creation unwindFromTask()")

  /
  if project.tasks != nil {
  tasks = project.tasks?.allObjects as! [TaskMO]
  tasks = tasks.sorted(by: { $0.name! < $1.name! })
  print("unwindFromTask() tasks.count: " + String(tasks.count))

  for task in tasks {
  print(task.name!)
  }
  }
  */

  tableView.reloadData()
  }
}


Any ideas why it is doing that?


Max

Accepted Reply

Ok, found the root cause. I was missing


case .update:
  if let indexPath = indexPath {
  let task = fetchResultController.object(at: indexPath)
  let cell = tableView.cellForRow(at: indexPath)
  cell?.textLabel?.text = task.name
  }
  case .move:
  if let indexPath = indexPath {
  tableView.deleteRows(at: [indexPath], with: .fade)
  }

  if let newIndexPath = newIndexPath {
  tableView.insertRows(at: [newIndexPath], with: .fade)
  }


in


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

Replies

Ok, found the root cause. I was missing


case .update:
  if let indexPath = indexPath {
  let task = fetchResultController.object(at: indexPath)
  let cell = tableView.cellForRow(at: indexPath)
  cell?.textLabel?.text = task.name
  }
  case .move:
  if let indexPath = indexPath {
  tableView.deleteRows(at: [indexPath], with: .fade)
  }

  if let newIndexPath = newIndexPath {
  tableView.insertRows(at: [newIndexPath], with: .fade)
  }


in


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

Great you found it.


Don't forget to mark your thread as clased.