My app has crashed multiple times while implementing a swipe to delete feature.
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, 1 deleted) and plus or minus the
number of rows moved into or out of that section (0 moved in, 0 moved
out).'
I read somewhere that this has to do with rows not being decremented by 1 at numberOfRowsInSection so I created a var deleted :Bool? to change whenever a data / row is deleted. Now, I just swipe and delete once and my app crashes after another attempt.
Here are the codes related to this:
var myFavs : [MyFavourites]?
var deleted :Bool?
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
if deleted == true {
return myFavs!.count - 1
} else {
return (myFavs?.count)!
}
}
// Override to support editing the table view.
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
tableView.beginUpdates()
//Delete from CD.
let appDel: AppDelegate = UIApplication.sharedApplication().delegate as! AppDelegate
let context: NSManagedObjectContext = appDel.managedObjectContext
let myFavo = myFavs![indexPath.row]
context.deleteObject(myFavo as MyFavourites)
do {
try context.save()
} catch _ {
}
// Delete the row from the data source
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
deleted = true
tableView.endUpdates()
tableView.reloadData()
}
}
[Edit]
My Data source var myFavs : [MyFavourites]? is a generated NSManagedObject subclass from CoreData. I forgot to mention that it deletes successfully from coreData when I restart the App from Simulator.
Here's my Data Source code if necessary.
extension MyFavourites {
#NSManaged var id: String?
}
What am I doing wrong ?
Related
I have a dozen table view controllers that all work as expected, then I have this one which crashes with:
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, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
The code doing this has been modified several times with different options but to no effect.
import UIKit
import CoreData
class QTypeVCY: UITableViewController, NSFetchedResultsControllerDelegate
{
let app = UIApplication.shared.delegate as! AppDelegate
override func viewDidLoad()
{
super.viewDidLoad()
}
override func numberOfSections(in tableView: UITableView) -> Int
{
let sections = frc.sections
return sections!.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
guard let sections = self.frc.sections else
{
fatalError("No sections in fetchedResultsController")
}
let sectionInfo = sections[section]
return sectionInfo.numberOfObjects
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "QQQQ", for: indexPath)
let qtype = frc.object(at: indexPath)
cell.textLabel?.text = qtype.title
return cell
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete
{
do
{
let qtype = frc.object(at: indexPath)
let context = self.frc.managedObjectContext
context.delete(qtype)
try context.save()
tableView.deleteRows(at: [indexPath], with: .fade)
}
catch
{
debugPrint(error)
}
} else if editingStyle == .insert
{
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
lazy var frc: NSFetchedResultsController<Qtype> =
{
let context = self.app.persistentContainer.viewContext
let req: NSFetchRequest<Qtype> = Qtype.fetchRequest()
req.fetchBatchSize = 10
let sortDescriptor1 = NSSortDescriptor(key: #keyPath(Qtype.specialty), ascending:true)
let sortDescriptor2 = NSSortDescriptor(key: #keyPath(Qtype.title), ascending:true)
req.sortDescriptors = [sortDescriptor1, sortDescriptor2]
let afrc = NSFetchedResultsController(fetchRequest: req, managedObjectContext: context, sectionNameKeyPath: nil, cacheName: nil)
afrc.delegate = self
do
{
try afrc.performFetch()
}
catch
{
print(error.localizedDescription)
fatalError("Abort while fetching Qtype")
}
return afrc
}()
}
The crash occurs on the tableview.deleteRows statement. I have tried surrounding the code with beginUpdate/endUpdates, with and without the performFetch, even tried re-entering the code in case I had a typo that I missed. This same basic code is working fine on other tables/view controllers, just this one.
The entity is just made up of strings. I had it with and without relationships to other entities.
The row is deleted since the next time I run the app it is missing. Also, one other thing about this table is that calling reloadData after adding a new object does not add the row. I need to leave the tableview and reenter it. I'm sure the two are related but can't say why.
Since originally posting this, I included the entire VC code instead of just the offending code. I have also tried swapping the Entity with a different entity where this issue does not occur, but the program still crashes even with a different entity.
You forgot to remove the item also from the data source array
let qtype = qtypes[indexPath.row]
let context = self.frc.managedObjectContext
context.delete(qtype)
try context.save()
qtypes.remove(at: indexPath.row) // <--
tableView.deleteRows(at: [indexPath], with: .fade)
I wish I could offer the actual solution, but the above code now works. I had updated XCode to the 1/24/18 version but I don't think that is the solution. I also stripped out a few things I had in the original code that crashed, but then I put them back and it still works. I suspect I had a typo somewhere, but after spending a half hour trying to locate it, I'm declaring victory and moving on.
I just solloved a problem similar to yours.
when deleted data in CoreData, TableView data array also delete before appDelegate.saveContext()
Code in func tableView(_ tableView: UITableView, trailingSwipeActionsConfigurationForRowAt indexPath: IndexPath)
self.tableviewArrayData.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .fade)
appDelegate.saveContext()
So I've my app crashing everytime I try to delete the last cell of the section.
Ex., if my section has 10 rows, I can delete them without any problem but the last one throws the following error:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of sections. The number of sections contained in the table view after the update (1) must be equal to the number of sections contained in the table view before the update (3), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted).'
I've searched around here and found some ways to fix this problem, but I tried them all and couldn't get this issue fixed, it still crashes and throws the same error.
The related parts of my code goes as it follows:
override func numberOfSections(in tableView: UITableView) -> Int {
if (main_arr.count > 0 && sub_arr.count > 0) {
self.numberOfSections = 3
} else {
self.numberOfSections = 2
}
return self.numberOfSections
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (section == 0) {
return 1
} else if (section == 1 && main_arr.count > 0) {
return main_arr.count
} else {
return sub_arr.count
}
}
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
// Override to support editing the table view.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
switch indexPath.section {
case 1:
main_arr.remove(at: indexPath.row)
case 2:
sub_arr.remove(at: indexPath.row)
default:
print("default 001")
}
tableView.deleteRows(at: [indexPath], with: .fade)
} else if editingStyle == .insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
EDIT 1
I tried to handle the global variable, responsible for the numberOfSections so when any of my arrays.count == 0 it will be decreased by 1, but it didn't solved the issue.
I do understand completely the error message, like, if I've 3 sections, and I delete the whole content of one of them, I should delete one section and delete it from the datasource as well.
The problem is that numberOfSections returns different values before and after you delete rows, but you don't delete any sections. So you should either return a constant value in numberOfSections or call deleteSections in addition to deleteRows
The main thing to remember is the following:
UITableView must always contain the same number of rows and sections as your dataSource.
You cannot just return a new value in numberOfSections or numberOfRows dataSource methods. Every change should be compensated with delete/insert rows(sections) method. And vice versa: if you delete/insert to tableView, you must return corresponding values in dataSource methods. Just as your exception message states:
The number of sections contained in the table view after the update
(1) must be equal to the number of sections contained in the table
view before the update (3), plus or minus the number of sections
inserted or deleted (0 inserted, 0 deleted).
It's because 3+0≠1. In this case you should have deleted two sections to avoid crash.
The error message is actually very helpful. It talks about sections - it expected not to change, but it found a different value after your operation.
Basically, UIKit did check the number of sections and rows before calling your code, runs your code, and then checks if the the new numbers line up with your operations. In you case, it doesn't - because you are calling deleteRows (which should decrease the row count for the given section), but your numberOfSections delegate gives a different result now. So you either want to call deleteSections or keep the sectionCount unmodified.
BTW, it is not forbidden to return 0 as the number of rows for a section - maybe this is just what you want here.
A further note: I don't think that storing the number of sections in an instance variable is a good thing here. It makes for double bookkeeping.
I have a dynamic TableView and want to delete rows with an animation.
My Code:
struct TableItem {
let text: String
let id: String
let creationDate: String
let bug: String
let comments: String
}
var sections = Dictionary<String, Array<TableItem>>()
var sortedSections = [String]()
//Some other Code
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
}
override func tableView(tableView: UITableView, editActionsForRowAtIndexPath indexPath: NSIndexPath) -> [UITableViewRowAction]? {
let setChecked = UITableViewRowAction(style: UITableViewRowActionStyle.Default, title: "Erledigt" , handler: { (action:UITableViewRowAction!, indexPath:NSIndexPath!) -> Void in
var tableSection = self.sections[self.sortedSections[indexPath.section]]
let tableItem = tableSection![indexPath.row]
//Not important HTTP POST Code
if(success == 1)
{
NSLog("SUCCESS");
self.tableView.beginUpdates()
tableSection?.removeAtIndex(indexPath.row)
self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Left)
self.tableView.endUpdates()
//self.getChecked()
}
}
return [setChecked]
}
If I run this Code I get the following error-message:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 6. 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 (2), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
I have no idea what I'm doing wrong.
Thanks for your help.
That's the value type trap.
Swift collection types are structs which have value semantics unlike classes which have reference semantics.
The line var tableSection = self.sections[self.sortedSections[indexPath.section]] makes a copy of the object and leaves self.sections unchanged.
After removing the item from the list you have to assign the array back to self.sections.
tableSection?.removeAtIndex(indexPath.row)
self.sections[self.sortedSections[indexPath.section]] = tableSection
okay im making an app where you add items to a cart and it updates as you add. i do all the adding fine, but i cant get deleting to work. in the cart cell i would need to remove from 3 arrays, but that doesn't seem to be the problem because i keep getting this error:
"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, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'"
i'm not really sure what i'm doing wrong here. i've followed a few tutorials and they all seem to be doing the same thing. any help is appreciated thanks in advance.
heres some of my code:
//declaring arrays to show in cell
var cartKeys = [String]()
var cartValues = [String]()
var deets = [[String]]()
var count = 0
//populating arrays from original arrays
func populateCart(){
for (key,value) in MenuStore.shared.cart {
self.cartKeys.append(key)
self.cartValues.append(value)
}
for i in MenuStore.shared.cartAndItems{
for (k,v) in i {
deets.append(v)
}
}
print("DEETS \(deets)")
for c in cartView.visibleCells {
count += 1
}
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
switch tableView{
case cartView:
if editingStyle == .delete{
let index = indexPath.row
print(index)
cartKeys.remove(at: index)
cartValues.remove(at: index)
deets.remove(at: index)
tableView.deleteRows(at: [indexPath], with: .automatic)
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch tableView{
case cartView:
let cartCell = tableView.dequeueReusableCell(withIdentifier: "cartCell", for: indexPath) as? CartCell
cartCell?.cartTitle.text = cartKeys[(indexPath as NSIndexPath).row]
cartCell?.cartPrice.text = cartValues[(indexPath as NSIndexPath).row]
cartCell?.cartDetails.text = deets[count].joined(separator: ", ")
return cartCell!
ps. if anyone wants to help with replacing the visible cells call thatd be great. id like to loop through all the cells even if their not visible on the screen.
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, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
Here's the code:
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == UITableViewCellEditingStyle.Delete {
let delegate = UIApplication.sharedApplication().delegate as! AppDelegate
let managedContext = delegate.managedObjectContext!
var error: NSError?
let fetchRequest = NSFetchRequest(entityName: "Task")
let fetchedResults = managedContext.executeFetchRequest(fetchRequest, error: &error) as! [NSManagedObject]
managedContext.deleteObject(fetchedResults[indexPath.row])
if managedContext.save(&error) == true {
println("Yes, you did it!")
}
//All the above code works fine.
table.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Fade)
}
}
UPDATE:
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return tasks.count
}
You have to update the tasks variable before deleteRowsAtIndexPaths be called:
managedContext.deleteObject(fetchedResults[indexPath.row])
tasks.removeAtIndex(indexPath.row)
table.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Fade)
Problem is you delete data from core data but that data is still in your source array.
you have to delete that data from your source array before delete row