Strange crash on row deletion from UITableView - ios

I have an array:
var users = [Users]()
And in editActionsForRowAt I do following:
self.viewModel.users.remove(at: indexPath.row)
self.usersTableView.deleteRows(at: [indexPath], with: .automatic)
and it crashes on deleteRows with the next error:
attempt to delete row 0 from section 0 which only
contains 0 rows before the update
I tried to debug and found the next: when I remove a user from the array:
self.viewModel.users.remove(at: indexPath.row)
it removes somehow the row also! Like before this line
self.usersTableView.numberOfRows(inSection: 0)
gives me 1. After deletion from the array BUT before deleteRows it becomes equal to0`. I cannot understand the problem. It's so weird. Can someone help me to understand it?
UPDATE
numberOfRowsInSection:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.viewModel.users.count
}

Related

Invalid update: invalid number of rows in section UITableView problem

I am playing with a table as below (I've seen multiple threads on this topic but couldn't answer my question):
func numberOfSections(in _: UITableView) -> Int {
3
}
func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
if section == 0 {
return 3
} else if section == 1 {
return 4
} else {
return 2
}
}
Then after clicking on a cell I want a new cell inserted into the same section at row index 0. I wrote the below:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.insertRows(at: [IndexPath(row: 0, section: indexPath.section)], with: .left)
}
I get this error:
Exception NSException * "Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (3) must be equal to the number of rows contained in that section before the update (3), plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out)."
I assume I should somehow update the data source, but since in my case I return const values for rows in sections is there a way to update the the number of rows in section? (the numberOfRows in section only returns immutable value). Thank you
in my case I return const values
There you go! If you are going to modify the number of rows you need variables
For example
var sections = [3,4,2]
func numberOfSections(in _: UITableView) -> Int {
return sections.count
}
func tableView(_: UITableView, numberOfRowsInSection section: Int) -> Int {
return sections[section]
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
sections[indexPath.section] += 1
tableView.insertRows(at: [IndexPath(row: 0, section: indexPath.section)], with: .left)
}

Delete rows from tableview using swift

I make a table view on my application, I want to add option that could the user delete the row in tableview.
I'm using this func
h1 is my array string that I put it in tableview.
my error is when I'm try to delete row in func editinstyle
var h1:[String] = ["one","two","three"]
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
h1.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell = self.tableView.dequeueReusableCell(withIdentifier: "cell3", for: indexPath as IndexPath) as! TableView1
cell.information.text = h1[indexPath.row]
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
print("Deleted")
h1.remove(at: indexPath.row) //Remove element from your array
print(h1)
tableView.deleteRows(at: [indexPath], with: .fade)
}
}
when I try to click to Delete it show like this 2020-02-11 20:43:35.803024+0200 TraniersApp[13314:401189] *** 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 (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).'
Does someone know where is the problem?
To define "[indexPath]". It becomes [IndexPath(row: indexPath.row, section: 0)]
tableView.deleteRows(at: [IndexPath(row: indexPath.row, section: 0)], with: .automatic)
or
recall the particular section in the table view also
This will work outside of the function
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath)
Suspect this is a race condition that can be resolved by performing the h1.remove() and tableView.deleteRows() within a transaction (i.e., between tableView.beginUpdates() and tableView.endUpdates()).
tableView.beginUpdates()
h1.remove(at: indexPath.row) //Remove element from your array
tableView.deleteRows(at: [indexPath], with: .fade)
tableView.endUpdates()

Unable to delete row from table view

I have a table view with a certain number of rows. With a button on the same ViewController, I want to pop out one of the tableView rows. However, I am unable to do so. Note that I am not using editing style of table view but using a button on the same ViewController to delete a row from the tableView.
func postAction() {
postTable.deleteRows(at: [IndexPath(row: 0, section: 0)] , with: .fade)
}
However this results in crash. I am not sure why is this happening and how I should correct this. I intend to delete the first row from the only section here in the tableView (postTable)
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 (3) must be equal to the number of rows contained in that section before the update (3), 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).'
My code:
postTable.delegate = self
postTable.dataSource = self
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = JobPostCellView(style: UITableViewCellStyle.default , reuseIdentifier: "PostCell")
cell.delegate = self
cell.name = arr[indexPath.row]
return cell
}
try this
func postAction() {
let indexPath = IndexPath(row: 0, section: 0)
yourmodel.remove(at: indexPath.row)//update your model also here
postTable.deleteRows(at: [indexPath] , with: .fade)
}
this may be happenig because you tableview row is not matching with the returning rows from number of rows in section method of your data source
for example
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
if you are returning 10 in your numberOfRowsInSection then there will be 10 rows in table
now you are deleting 1 row from it so the rows will be 9 but your numberOfRowsInSection method still returning 10 so this will create inconsistency so you have to update your count in numberOfRowsInSection section to return 9
and if you are using dynamic array than you have to delete it from your array also like in answer

How can I add/remove cells in a table view more elegantly?

I think this is probably an X Y problem, so I'll give some background info first.
I am building an app that can show a "form". The user can fill in the form with some stuff and create some custom things.
I think the most suitable thing to do is to use a table view for the form. And I can display all the text boxes that need to be fill in, in each of the table cells.
Here's a screenshot:
The "Add New Input" button will insert a new cell on the bottom when it is tapped. And if you swipe one of the inputs to the left, you get a "Delete" button. You can use that to delete the input.
As you can see, this table view needs to add and delete rows.
Originally, I was using a cocoapod called "TableViewModel" which makes this super easy. But then I found a really severe bug in the library so I don't want to use it anymore.
I tried using the table view's deleteRowsAtIndexPaths and insertRowsAtIndexPaths methods. But if I do it like this:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = UITableViewCell()
cell.textLabel?.text = "Hello"
return cell
}
// I use motionEnded here because I don't want to add a button or anything just to test this
override func motionEnded(motion: UIEventSubtype, withEvent event: UIEvent?) {
tableView.deleteRowsAtIndexPaths([NSIndexPath(forRow: 0, inSection: 0)], withRowAnimation: .Fade)
}
It will result in an exception saying that the table view is inconsistent with the data source after I deleted one row. This means I have to have code to handle this inconsistency. This will definitely make my code harder to read.
The same thing goes with the insert method.
Also, I need to keep track of how many rows there are in each section. And my code just becomes really messy and unmaintainable with all that.
I have also searched for other libraries but they are all really weird and not as straightforward as "tableViewModel". They all seem to require me to create a model for the table view. I don't understand how to do that in my case. I just want to display a bunch of text fields!
How can I insert or delete rows more elegantly? I think I either need to write an extension of the table view or learn to write a model for my table view. But I am able to do neither of these methods.
Because you don't have any data source. Also see logically: suppose you add a new row with insertRowsAtIndexPaths . So now you have 2 rows. However your numberOfRowsInSection always returning 1. so it will crash and vice versa for delete. Table view are supposed to work with a collection (NSArray, NSDictionary, NSSet etc.).
For your help:
Already they have nade a form as yours
obj-c easy to understand how table view with data source work
Adding, Updating, Deleting and Moving records using Swift.
You may use a TableView to create the form but you must design an appropriate dataSource to keep track of these data. Now the dataSource method must be modified to
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return yourDataSourceForSection.count
}
You may edit this dataSource when a delete operation is performed as follows
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == .Delete {
//Delete the row from the data source to ensure consistency
self.array.removeAtIndex(indexPath.row)
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Fade)
}
}
You may use a model class as the dataSource.
class NewCustomOperation: NSObject {
var name: String?
var rejectFloatingPoints: Bool?
var inputs: AvailableInputs?
var results: Results?
}
class AvailableInputs: NSObject {
var name: [String]?
var description: [String]?
}
class Results: NSObject {
var name: [String]?
var formula: [String]?
}
I found a solution!
I basically keep an array of an array of cells for the table view to display:
var cells: [[UITableViewCell]] = [[], [], []] // I have 3 sections, so 3 empty arrays
And then I added these data source methods:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return cells.count
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return cells[section].count
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
return cells[indexPath.section][indexPath.row]
}
Now, I can add and remove cells super easily:
func addCellToSection(section: Int, index: Int, cell: UITableViewCell) {
cells[section].insert(cell, atIndex: index)
tableView.insertRowsAtIndexPaths([NSIndexPath(forRow: index, inSection: section)], withRowAnimation: .Left)
}
func removeCellFromSection(section: Int, index: Int) {
cells[section].removeAtIndex(index)
tableView.deleteRowsAtIndexPaths([NSIndexPath(forRow: index, inSection: section)], withRowAnimation: .Left)
}
With just two lines, I can add/remove cells with animation!

By NSInternalInconsistencyException claimed "before the update" value wrong

I get a NSInternalInconsistencyException exception. It says : '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 (1 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).
It says before the update numberOfRowsInSection was 1, but it is not how I can see during debugging. Before the update, last time when numberOfRowsInSection was called it returned 0.
Do you have any idea?
this is what I see:
numberOfRowsInSection return 0
self.tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade)
numberOfRowsInSection get called and return 1
crash
my solution has two things: (1) add beginUpdates / endUpdates (2) add a bool variable to check whether view was already loaded or not
more explanation: if beginUpdates triggers a view reload cycle then viewcontroller will note that datasource already increased, so no insertRowsAtIndexPaths needs, if beginUpdates not triggers view reload cycle then neigter numberOfRowsInSection will be called so insertRowsAtIndexPaths can be called
func controller(controller: NSFetchedResultsController!, didChangeObject anObject: AnyObject!, atIndexPath indexPath: NSIndexPath!, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath!) {
if controller == frc {
switch(type) {
case .Insert:
isLayoutReloaded = false
self.tableView.beginUpdates() //<- !!!
if !isLayoutReloaded { //<- !!!
self.tableView.insertRowsAtIndexPaths([newIndexPath], withRowAnimation: .Fade)
}
self.tableView.endUpdates() //<- !!!
}
}
}
override func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -> Int {
isLayoutReloaded = true //<- !!!
let numberOfRowsInSection = ((frc!.sections as NSArray)[0] as NSFetchedResultsSectionInfo).numberOfObjects
return numberOfRowsInSection
}

Resources