What I am trying to do is reordering my tableview cell. My tableview has sections: the data model the simple : list of product and each product has a category.
I am using UITableViewDropDelegate,UITableViewDragDelegate because I don't like the editing mode in tableview (the red button drive me crazy):
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
var objects = self.fetchedResultsController.fetchedObjects! as [TblProduits]
self.fetchedResultsController.delegate = nil
//get the destination section
let myDestSection = objects[destinationIndexPath.row]
let object = objects[sourceIndexPath.row]
objects.remove(at: sourceIndexPath.row)
//updating product categorie with the destination categorie before inserting
object.categorie = myDestSection.categorie
objects.insert(object, at: destinationIndexPath.row)
fctSaveListDesProduits()
self.fetchedResultsController.delegate = self
}
My app crash on this function when I try to drop a product in another section with this error:
due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (5) must be equal to the number of rows contained in that section before the update (5), 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 (1 moved in, 0 moved out).'
How can I resolve this?
Here is the full code:
@IBOutlet var TblView_Produit: UITableView!
let Mycontext = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
let Myrequest : NSFetchRequest<TblProduits> = TblProduits.fetchRequest()
var fetchedResultsController : NSFetchedResultsController<TblProduits>!
var managedObjectContext: NSManagedObjectContext? = nil
override func viewDidLoad() {
super.viewDidLoad()
TblView_Produit.dragDelegate = self
TblView_Produit.dropDelegate = self
TblView_Produit.dragInteractionEnabled = true
// Load Data
Fct_loadListDesProduits()
fetchedResultsController.delegate = self
}
func Fct_SaveListDesProduits() {
do {
try Mycontext.save()
// print ("saved ligne \(i)")
} catch {
debugPrint ("there is an error \(error.localizedDescription)")
}
}
func Fct_loadListDesProduits () {
let MySortDescriptor = NSSortDescriptor(key: #keyPath(TblProduits.categorie.categorie_Name), ascending: true)
Myrequest.sortDescriptors = [MySortDescriptor]
fetchedResultsController = NSFetchedResultsController(fetchRequest: Myrequest, managedObjectContext: Mycontext, sectionNameKeyPath: #keyPath(TblProduits.categorie.categorie_Name), cacheName: nil)
do {
try fetchedResultsController.performFetch()
} catch {
debugPrint ("there is an error \(error.localizedDescription)")
}
}
func numberOfSections(in tableView: UITableView) -> Int {
guard let sections = self.fetchedResultsController.sections else {
return 0
}
return sections.count
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let sections = self.fetchedResultsController.sections else {
return 0
}
return sections[section].numberOfObjects
}
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
guard let sections = self.fetchedResultsController.sections else {
return ""
}
return sections[section].name
}
// MARK: - FetchedResultsController Delegate
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
switch(type) {
case .insert:
if let Myindexpath = newIndexPath {
self.TblView_Produit.insertRows(at: [Myindexpath], with: .left)
}
break;
case .delete:
if let MyindexPath = indexPath {
TblView_Produit.deleteRows(at: [MyindexPath], with: .fade)
}
break;
case .move:
break;
case .update:
break;
}
}
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
TblView_Produit.endUpdates()
}
func controllerWillChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
TblView_Produit.beginUpdates()
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") as! ProduitTableViewCell
if let ProduitName = self.fetchedResultsController.object(at: indexPath).produit_name { //ProductTable[indexPath.row].produit_name{
cell.SetListeDesProduits(ProduitName: ProduitName)
}
// cell.isEditing = self.tableView(tableView, canMoveRowAt: indexPath)
return cell
}
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
var objects = self.fetchedResultsController.fetchedObjects! as [TblProduits]
self.fetchedResultsController.delegate = nil
//get the destination section
let myDestSection = objects[destinationIndexPath.section]
let object = objects[sourceIndexPath.row]
objects.remove(at: sourceIndexPath.row)
//updating product categorie with the destination categorie before inserting
object.categorie = myDestSection.categorie
objects.insert(object, at: destinationIndexPath.row)
fctSaveListDesProduits()
self.fetchedResultsController.delegate = self
}
Logging numberOfrowinsection:
I have only three sections and each section has only one item
section : 2
sections[section].numberOfObjects: 1
section : 0
sections[section].numberOfObjects: 1
section : 1
sections[section].numberOfObjects: 1
section : 2
sections[section].numberOfObjects: 1
section : 0
sections[section].numberOfObjects: 1
section : 1
sections[section].numberOfObjects: 1
section : 2
sections[section].numberOfObjects: 1
section : 0
sections[section].numberOfObjects: 1
section : 1
sections[section].numberOfObjects: 1
section : 2
sections[section].numberOfObjects: 1
section : 0
sections[section].numberOfObjects: 1
section : 1
sections[section].numberOfObjects: 1
When I do the drag drop from the item from the second section to the first
section
section : 0
sections[section].numberOfObjects: 1
section : 1
sections[section].numberOfObjects: 1
section : 2
sections[section].numberOfObjects: 1
018-01-26 19:56:05.492956+0100 ShopTogether[8815:1168659] *** Assertion failure in -[UITableView _endCellAnimationsWithContext:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit_Sim/UIKit-3698.33.6/UITableView.m:2011
2018-01-26 19:56:05.513608+0100 ShopTogether[8815:1168659] *** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (1) 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 (1 moved in, 0 moved out).'
// Update Fetchcontroller
I think that the problem is when I display the sections
In fact when I put nil in sectionNameKeyPath: nil ==> my table view has no sections any more and I have no crash any more but I want sections in my table view.
fetchedResultsController =
NSFetchedResultsController(fetchRequest: Myrequest, managedObjectContext: Mycontext,
sectionNameKeyPath: nil, cacheName: nil)
I am still searching for an answer, but I hope that drag and drop between different sections is allowed in tableview.
I am not sure this will be the cleanest way but I solved my problem by regenerating the TableView and inserting object after updating the category.
func tableView(_ tableView: UITableView, moveRowAt sourceIndexPath: IndexPath, to destinationIndexPath: IndexPath) {
self.fetchedResultsController.delegate = nil
var objects = self.fetchedResultsController.fetchedObjects
let objectSource = fetchedResultsController.object(at: IndexPath(row: sourceIndexPath.row, section: sourceIndexPath.section))
let MyCategorie = fetchedResultsController.object(at: IndexPath(row: 0, section: destinationIndexPath.section)).categorie
objectSource.categorie = MyCategorie
objects?.remove(at: sourceIndexPath.row)
objects?.insert(objectSource, at: destinationIndexPath.row)
Fct_SaveListDesProduits()
self.fetchedResultsController.delegate = self
// REGENRATING TABLEVIEW
Fct_loadListDesProduits()
}