I read all of the related posts about this problem, but I am still having an error:
'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (13) must be equal to the number of rows contained in that section before the update (13), plus or minus the number of rows inserted or deleted from that section (11 inserted, 0 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
Here is the code:
func appendItems(entities: [Entity]) {
if entities.count > 0 {
let entitesFirstPos = items.count
self.items.append(contentsOf: entities)
var indexPaths: [IndexPath] = []
for i in entitesFirstPos..<items.count {
indexPaths.append(IndexPath(row: i, section: 0))
}
self.tableView.beginUpdates()
self.tableView.insertRows(at: indexPaths, with: .none)
self.tableView.endUpdates()
}
}
Looks like appendItems() is a function in a subclass of UITableViewController. If that's the case, try the following. It's impossible to give an exact answer without knowing what your tableView(UITableView, numberOfRowsInSection: Int) and tableView(UITableView, cellForRowAt: IndexPath) look like. So this is based on the assumption that self.items is simply an array that contains the data for all the cells in section 0.
First try to make this change:
//self.tableView.beginUpdates()
//self.tableView.insertRows(at: indexPaths, with: .none)
//self.tableView.endUpdates()
self.tableView.reloadData()
Making that change would update your table view without performing the row insert animation as a batch for all the new rows. If that works your issue is just within the appendItems() function. In that case try to make these changes:
func appendItems(entities: [Entity]) {
if entities.count > 0 {
self.tableView.beginUpdates() // <--- Insert this here
let entitesFirstPos = items.count
self.items.append(contentsOf: entities)
var indexPaths: [IndexPath] = []
for i in entitesFirstPos..<items.count {
indexPaths.append(IndexPath(row: i, section: 0))
}
//The following line is now at the top of the block.
//self.tableView.beginUpdates()
self.tableView.insertRows(at: indexPaths, with: .none)
self.tableView.endUpdates()
}
}
Making this change will make sure that if the number of rows in a section is queried by the UITableView before the call to the beginUpdates() function or even by that function itself the correct number of rows will be returned. In your current setup, assuming that self.items represents the table view data, the updated number of rows will be visible already before beginUpdates() is called. If that doesn't work, some more of your code is needed to pinpoint the issue.
Related
I've implemented the following deleting mechanism: on delete action tableView deletes selected rows and if section does not have any rows it will be deleted.
My mechanism looks like this:
guard let selectedRows = self.tableView.indexPathsForSelectedRows,
let indexPath = self.tableView.indexPathForSelectedRow else { return }
self.tableView.beginUpdates()
self.dataSource[indexPath.section].rows.remove(at: indexPath.item)
if self.dataSource[indexPath.section].rows.isEmpty {
self.dataSource.remove(at: indexPath.item)
self.tableView.deleteSections(IndexSet(arrayLiteral: indexPath.section), with: .fade)
}
self.tableView.deleteRows(at: selectedRows, with: .fade)
self.tableView.endUpdates()
My dataSource object:
struct DataSource {
var section:String
var rows:[Row]
}
struct Row {
var data:String
}
The problem is when I select more than one row and try to delete selected rows I've got a crush with following message:
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 (6), plus or minus the number of rows inserted or deleted from that section (0 inserted, 2 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'
While I delete by one row my mechanism works fine, as expected. Even deletes an empty section. Any thoughts, where am I wrong? Thanks a lot.
Solution: just add inverted indexPath array. let invertedIndexPathes = selectedRows.sorted(by: >)
Fixed mechanism:
guard let selectedRows = self.tableView.indexPathsForSelectedRows,
let indexPath = self.tableView.indexPathForSelectedRow else { return }
let invertedIndexPathes = selectedRows.sorted(by: >)
self.tableView.beginUpdates()
for itemIndexPath in invertedIndexPathes {
self.dataSource[itemIndexPath.section].rows.remove(at: itemIndexPath.row)
self.tableView.deleteRows(at: [itemIndexPath], with: .fade)
self.tableView.reloadData()
}
if self.dataSource[indexPath.section].rows.isEmpty {
self.dataSource.remove(at: indexPath.item)
self.tableView.deleteSections(IndexSet(arrayLiteral: indexPath.section), with: .fade)
}
self.tableView.endUpdates()
I have an array I need to add and remove cell data on click of "close" which is UIButton in header section
I am looping the array and getting row and for section I have used static data and appending in indexPaths then after
tblSideNav.deleteRows(at: indexPaths, with: .fade)
as above line start executing app got crash
var menusArray = [sideNavStruct(isOpend: true, menuImg: "user", menuTitle: "Profile", subMenu: ["My Profile", "Distributor Profile"]), sideNavStruct(isOpend: true, menuImg: "user", menuTitle: "Reports", subMenu: ["Stock Report", "NFR Report", "RTD”])]
#objc func handleOpenCloseCell() {
let section = 0
var indexPaths = [IndexPath]()
for row in menusArray[section].subMenu.indices {
let indexPath = IndexPath(row: row, section: section)
indexPaths.append(indexPath)
}
print(indexPaths.count)
print(menusArray[section].subMenu[0])
menusArray[section].subMenu[0].removeAll()
tblSideNav.deleteRows(at: indexPaths, with: .fade)
}
my app got crash on tblSideNav.deleteRows(at: indexPaths, with: .fade):
Invalid update: invalid number of rows in section 0. The number of
rows contained in an existing section after the update (7) 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, 2 deleted) and plus or minus the number of
rows moved into or out of that section (0 moved in, 0 moved out).
I am not able to find the issue
First of all, you are calling deleteRows without beginUpdates and endUpdates which is required.
tableView.beginUpdates()
tableView.deleteRows(at: indexPaths, with: .fade)
tableView.endUpdates()
The same error can occur if your delegate function is not returning the correct number of rows after the update.
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return rowCount
}
I believe that better practice would be to change/empty the variable where you're storing your rows information/data and simply call reloadData().
So my numberOfRowsInSection is:
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let delegate = delegate else { return 0 }
return delegate?.comments.count + 1
}
I added one more to compensate for the "load more" which is on top of the table view (in this case, it's row 0).
Here's the thing: when I want to remove that cell "load more" after fetching the max amount of posts, it gives me an error:
The number of rows contained in an existing section after the update (12) must be equal to the number of rows contained in that section before the update (11), 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).'
So here's the function that causes the error:
func loadMore() {
// loadMoreComments(completion:) inserts the new comment objects inside
// the data source in the beginning of the list.
delegate?.loadMoreComments(completion: { (isEndOfPosts, newCommentCount, comments) in
self.didReachEndOfPosts = isEndOfPosts
if isEndOfPosts {
// indexPaths just returns [[0,1]], or, just one row.
let indexPaths = self.createIndexPathsForNoMorePosts(newCommentCount: newCommentCount)
self.tableView.beginUpdates()
// error happens here.
self.tableView.deleteRows(at: [IndexPath(row: 0, section: 0)], with: .automatic)
self.tableView.endUpdates()
// assume only one post comes in, and that's the last one.
self.tableView.beginUpdates()
self.tableView.insertRows(at: indexPaths, with: .automatic)
self.tableView.endUpdates()
}
}
What I'm trying to accomplish is this: once i get the last post, remove the "load more" cell, and insert the last few posts, replacing the 0 row with the first of the last few posts.
You need to notify your tableView's datasource about cell count change. Create a class variable, something like
var shouldShowLoadMoreCell = true
and than modify numberOfRowsInSection method
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
guard let delegate = delegate else { return 0 }
if shouldShowLoadMoreCell {
return delegate?.comments.count + 1
} else {
return delegate?.comments.count
}
}
finally, set this flag when needed
self.tableView.beginUpdates()
shouldShowLoadMoreCell = false
self.tableView.deleteRows(at: [IndexPath(row: 0, section: 0)], with: .automatic)
self.tableView.endUpdates()
I am just starting to learn to swift; I want to add a search bar that allows users to search through the data in my Firebase database. The search bar has added but the code to filter the search result keeps crashing with 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 (0) must be equal to the number of rows contained in that section before the update (0), 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).
The error is pointing to this line
self.tableView.insertRows(at: [IndexPath(row: self.businessArray.count-1, section: 0)], with: UITableViewRowAnimation.automatic)
override func viewDidLoad() {
super.viewDidLoad()
ref = FIRDatabase.database().reference(fromURL:"")
searchController.searchResultsUpdater = self
searchController.dimsBackgroundDuringPresentation = false
definesPresentationContext = true
tableView.tableHeaderView = searchController.searchBar
ref.child("Businesses").queryOrdered(byChild: "Basic-Info/business").observe(.childAdded, with: { (snapshot) in
//insert the rows
self.tableView.insertRows(at: [IndexPath(row: self.businessArray.count-1, section: 0)], with: UITableViewRowAnimation.automatic)
}) { (error) in
print(error.localizedDescription)
}
}
Before inserting the rows in the table view you have to insert the items at the same indexes into the data source array.
However in this case I'd recommend to assign the query result to the data source array and call reloadData()
As #vadian said, you have to modify your datasource to match what you are trying to reflect in the UI. So if we are inserting a row into a UITableView, then you must first insert an object into the datasource at the same index of the row you are adding. Like so:
// First modify datasource
let businessObjectToInsert = BusinessObject() // or whatever your class is
self.businessArray.append(businessObject)
// Then insert row into `UITableView`
self.tableView.insertRows(at: [IndexPath(row: self.businessArray.count-1, section: 0)], with: UITableViewRowAnimation.automatic)
I'm getting an error when I try to delete a cell from my UITableView. I've looked at numerous links (this one for example is what I'm going off of. Below is my code:
var countdownTime = expirDate - currentDate
timer.setCountDownTime(countdownTime)
println("Count : \(self.offers.count)")
timer.startWithEndingBlock { (countdownTime) -> Void in
//If the times have expired, then delete the row
self.tableView.beginUpdates()
self.offers.removeAtIndex(indexPath.row)
self.tableView.deleteRowsAtIndexPaths([indexPath.row], withRowAnimation: UITableViewRowAnimation.Fade)
self.tableView.endUpdates()
}
timer.start()
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return offers.count
}
//Only one section
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
I want to remove the cell when my timer has completed and the offers array is the array that I use to populate the UITableView. I was going off of this Assertion Failure -[UITableView _endCellAnimationsWithContext] which I thought would help me delete/insert rows correctly but I still get an error saying:
*** 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 (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).'
Any help would be appreciated!
EDIT: An issue I might be having that I am trying to delete the cell while I am inserting the cell into the UITableView. I am inserting the cell, setting the cell contents, then deleting the cell if the timer for that cell has existed for over 24 hours. This is bad because the cell will then try to insert the next cell when it thinks that there is 1 cell in the table view. Are there any better ways to do this?
EDIT 2: Code for inserting into table:
...//Code to insert into database
override func viewDidLoad() {
super.viewDidLoad()
//setupOffers()
setupOffers { (result, offer) -> Void in
if(result == true){
//Insert each row one by one.
var currentCount = self.offers.count
var indexPaths: [NSIndexPath] = [NSIndexPath]()
indexPaths.append(NSIndexPath(forRow:currentCount, inSection: 0))
self.offers.append(offer)
currentCount++;
//Update based on each table view index.
self.tableView.beginUpdates()
self.tableView.insertRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.Bottom)
self.tableView.endUpdates()
//Method 2 to update uitableview
//self.tableView.reloadData()
}
}
// Do any additional setup after loading the view, typically from a nib.
self.tableView.delegate = self
self.tableView.dataSource = self
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.rowHeight = 145.0
}
Add the statement self.offers.removeAtIndex(indexPath.row) before starting the updates on the table view self.tableView.beginUpdates() as
self.offers.removeAtIndex(indexPath.row)
self.tableView.beginUpdates()
As it could happen that you start the update on table view and the number of rows are returned before deleting the object from the array, and thus could result in inconsistency.
I believe this is simply a caching problem. Your fetchedResultController is not going to automatically refetch your results as it caches its results. That means that when tableView:numberOfRowsInSection: is called again, the results count is still returning number even though you deleted an item.
Use this code...
self.tableView.beginUpdates()
self.tableView!.deleteRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
self.tableView.endUpdates()
Error when deleting item from tableview in swift
This might helps you :)
Try self.tableView.beginUpdates() after self.offers.removeAtIndex(indexPath.row)
EDIT:
Remove beginUpdates and endUpdates, as self.tableView.deleteRowsAtIndexPaths([indexPath.row], withRowAnimation: UITableViewRowAnimation.Fade) should be enough to refresh the table.
EDIT 2: Check that the count of offers is correct after the update. Print its count before and after you delete the item and after return offers.count.