1
votes

Main Objective: In first view controller I shall tap on a button in a cell -> button calls entity.removeFrom" "(_ value: " " ) method -> automatically should remove the corresponding cell from a table view in a second view controller.

What is Occurring: " " ... entity.removeFrom" "(_ value: " " ) method -> object is removed, set to nil, however NSFetchedResultsController is not deleting the respective rows. Leaving the tableview filled with empty cells ( from the remaining nil objects )

Question: How to remove many object in the to-many relationship and update the NSFetchedResultsController accordingly.

About the app Using swift 3 I am creating a basic To-Do list app with a tab bar controller.

First tab = A tableView of ToDoItem

Second tab = Finished toDoItems in an NSFetchedResultsController

Core Data Entities: FinishedCollection, ToDoItem

FinishedCollection has a one-to-many relationship with todoItems (destination is ToDoItem)

Order of operation:

First Tab

1) Set ToDoItem attribute isFinished

2) FinishedCollection.addToToDoItems(toDoItem) or FinishedCollection.removeFromToDoItems(toDoItem)

3) Automatically Update Second Tab NSFetchedResultsController

PROBLEM: When I removeFromToDoItems(toDoItem) the property in the NSFetchedResults controller is set to nil rather than removing the object entirely( I don't necessarily want to remove the object entirely) Resulting in empty cells and a million-frazillion nil objects. How do I approach removing the many nil objects in the one-to-many relationship and only having finished toDoItems in the NSFetchedResults controller tableview tab.

-

Attempt to filter nil objects when setting numberOfRowsInSection tableview method. Does not seem to do anything in this circumstance

let notNilObjects = sectionInfo.objects.flatMap { 0 }

TableViewDataSource methods

  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    let sections    = fetchedResultsController?.sections
    let sectionInfo = sections?[section]

    return sectionInfo?.numberOfObjects ?? 1
  }

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

    configure(cell, atIndexPath: indexPath)

    return cell
  }

//NSFetchedResultsDelegate methods

fileprivate func configure(_ cell: UITableViewCell, atIndexPath indexPath: IndexPath) {
  guard let selectedObject = fetchedResultsController?.object(at: indexPath) else {
    fatalError("Unexpected Object in FetchedResultsController")
  }

  let toDoItems = selectedObject.dayTasks?.allObjects as! [ToDoItem]
  let toDo = ToDoItems.last

  print(toDo?.name)
}

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

func controller(_ controller               : NSFetchedResultsController<NSFetchRequestResult>,
                didChange sectionInfo      : NSFetchedResultsSectionInfo,
                atSectionIndex sectionIndex: Int,
                for type                   : NSFetchedResultsChangeType) {
  switch type {
  case .insert:
    tableView.insertSections(NSIndexSet(index: sectionIndex) as IndexSet, with: .fade)
  case .delete:
    tableView.deleteSections(NSIndexSet(index: sectionIndex) as IndexSet, with: .fade)
  case .move:
    break
  case .update:
    tableView.reloadData()
  }
}

func controller(_ controller      : NSFetchedResultsController<NSFetchRequestResult>,
                didChange anObject: Any,
                at indexPath      : IndexPath?,
                for type          : NSFetchedResultsChangeType,
                newIndexPath      : IndexPath?) {
  switch type {
  case .insert:
    tableView.insertRows(at: [newIndexPath!], with: .fade)
  case .delete:
    tableView.deleteRows(at: [indexPath!], with: .fade)
  case .update:
    configure(tableView.cellForRow(at: indexPath!)!, atIndexPath: indexPath!)
  case .move:
    tableView.moveRow(at: indexPath!, to: newIndexPath!)
  }
}

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


func initializeFetchedResultsController() {
  let request: NSFetchRequest = ToDoItem.fetchRequest()
  request.sortDescriptors = [ NSSortDescriptor( key: "timestamp", ascending: true ),
                              NSSortDescriptor( key: "isAccomplished", ascending: true) ]

 fetchedResultsController = NSFetchedResultsController( fetchRequest        : request,
                                                       managedObjectContext: CoreDataHelper.context,
                                                       sectionNameKeyPath  : nil,
                                                       cacheName           : nil )
  fetchedResultsController?.delegate = self

  do {
    try fetchedResultsController?.performFetch()
  }
  catch {
    print("Couldn't fetch results controller")
  }
}
2
What does the predicate on the FetchedResultsController look like? Also why do you have a FinishedCollection instead of just relying on the isFinished property? - Jon Rose
I believe I had made the problem far more complicated than it should have been. Fetching the original entity and using its isFinished property to filter works just fine. HOWEVER, it seems all items in the Core Data context are going into the fetchedResultsController and cannot filter out the isFinished = false using a predicate. New problem to solve, any suggestions? - lifewithelliott
share your fetching code. - Jon Rose
@Jon Rose initializeFetchedResultsController added to bottom of code ^ - lifewithelliott
add a predicate to your NSFetchRequest to filter out the ToDoItem that you don't want to display - Jon Rose

2 Answers

2
votes

In your initializeFetchedResultsController method add

request.predicate = NSPredicate(format: "isFinished = 1")
1
votes

In order to filter an NSSet acquired from a fetchedResultsController one must use an NSPredicate and assign it to the request.predicate property of an NSFetchRequest

Example

let request = Entity.fetchRequest()

request.predicate = NSPredicate(format: "isAccomplished == YES")