I am hitting the following error when attempting to drag items from one UICollectionView (collectionView) into another (rackView):
'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of items in section 0. The number of items contained in an existing section after the update (7) must be equal to the number of items contained in that section before the update (6), plus or minus the number of items inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of items moved into or out of that section (0 moved in, 0 moved out).'
The strange thing is, almost the exact same code works for dragging and dropping when I am dragging the items from rackView into collectionView. rackView updates correctly when items are deleted from rack datasource but crashes when items are added back into it from the board (collectionView). Does anyone know why this is happening and is there any way to solve it?
Thanks for any information you can provide.
I've tried to add a variable which represents the count that's in the rack and return that in the numberOfItemsInSection method. This is stated as a possible solution for this error elsewhere but doesn't seem to resolve it for me.
private func moveItemsFromRack(coordinator: UICollectionViewDropCoordinator, destinationIndexPath: IndexPath, collectionView: UICollectionView)
{
collectionView.performBatchUpdates({
for (index, item) in coordinator.items.enumerated()
{
let indexPath = IndexPath(row: destinationIndexPath.row + index, section: destinationIndexPath.section)
self.board[indexPath.row] = item.dragItem.localObject as! String
self.rack.remove(at: self.sourceIndex.row)
}
DispatchQueue.main.async {
collectionView.reloadItems(at: [destinationIndexPath])
self.rackView.reloadData()
}
})
self.sourceIndex = []
}
private func moveItemsFromBoard(coordinator: UICollectionViewDropCoordinator, destinationIndexPath: IndexPath, collectionView: UICollectionView)
{
collectionView.performBatchUpdates({
for (index, item) in coordinator.items.enumerated()
{
let indexPath = IndexPath(row: destinationIndexPath.row + index, section: destinationIndexPath.section)
self.rack.insert(item.dragItem.localObject as! String, at: indexPath.row)
self.board[self.sourceIndex.row] = ""
}
DispatchQueue.main.async {
collectionView.reloadItems(at: [self.sourceIndex])
self.rackView.reloadData()
}
})
self.sourceIndex = []
}
I actually just removed the performBatchUpdates method and this issue was resolved.
An explanation for why is here:
https://fangpenlin.com/posts/2016/04/29/uicollectionview-invalid-number-of-items-crash-issue/
Related
When I remove an object from a list and try to add a new one in I get this error
2020-01-24 14:40:26.692343+1300 HappyDays[25416:1017241] *** 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 (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).'
I see other issues similar to this but I've tried implementing those solutions . (Similar issue , another similar issue)
Sections of my code:
Selecting the cell to delete if from the table
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
activeObjectives.actives.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .left)
}
Adding a new object to the table by tapping a button
#IBAction func getGoals(_ sender: UIButton) {
let newRowIndex = activeObjectives.actives.count
activeObjectives.setRandomGoalToActive(allObjectiveList: allGoalsArray)
addActiveGoalToactiveObjectives()
let indexPath = IndexPath(row: newRowIndex, section: 0)
let indexPaths = [indexPath]
activeGoalDisplay.insertRows(at: indexPaths, with: .automatic)
}
In case it's also needed, here is my addActiveGoalToactiveObjectives function called in the above
func addActiveGoalToactiveObjectives() {
for goal in allGoalsArray.goalsList {
if goal.isActive == true && activeObjectives.actives.contains(goal) == false {
activeObjectives.actives.append(goal)
}
}
}
Thanks in advance, any help is greatly appreciated
I found the issue. I wasn't setting an object value to false when removing it from the list. Now when I remove them I set the object to inactive.
activeObjectives.actives[indexPath.row].setInactive()
activeObjectives.actives.remove(at: indexPath.row)
tableView.deleteRows(at: [indexPath], with: .left)
}```
Following is my code to add section header view cell
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cell = tableView.dequeueReusableCell(withIdentifier: "menuHeaderTableViewCellID") as! MenuHeaderTableViewCell
cell.foodMenuItems = menuResult?.foodMenuItems?[section]
cell.setParentUI()
cell.expandCollapseClicked = {
[weak self]
(postiton) in
let isCollapsed = self?.menuResult?.foodMenuItems?[postiton].isCollapsed ?? false
self?.menuResult?.foodMenuItems?[postiton].isCollapsed = !isCollapsed
self?.tableViewMenu?.beginUpdates()
self?.tableViewMenu?.reloadSections([section], with: .fade)
self?.tableViewMenu?.endUpdates()
}
return cell
}
Following is code for count in each row and section
func numberOfSections(in tableView: UITableView) -> Int {
return (menuResult?.foodMenuItems?.count) ?? 0
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
let rowCount = menuResult?.foodMenuItems?[section].items?.count ?? 0
let isCollpased = menuResult?.foodMenuItems?[section].isCollapsed ?? false
return isCollpased ? 0 : rowCount
}
Following is code inside header view cell
#IBAction func expandCollapseClicked(_ sender: UIButton) {
guard let superView = self.superview as? UITableView else {
return
}
expandCollapseClicked?(superView.indexPath(for: self)?.section ?? 0)
}
I am facing issue on collapsing first section my header disappears and when I try to collapase other section getting following exception how to fix this?
*** 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 (25), 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 (0 moved in, 0 moved out).'
*** First throw call stack:
Problem may be in :
Avoid dequeueReusableCell. Try to use UIView with label and button.
expandCollapseClicked = {...}
should be called by UIButton. as it's sender is UIButton. Try to use:
view.button.expandCollapseClicked = {...}
Hope this will work.
I am building an iOS app, basically the user create an item by pressing the "+" button and then the app should put the new item in according section of the table based on the location of the item. However I got the error: Invalid update: invalid number of rows in section 1. 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. Here is my code, thank you
let sections = ["Bathroom", "Bedroom", "Dining Room", "Garage", "Kitchen", "Living Room"]
#IBAction func addNewItem(_ sender: UIBarButtonItem) {
/*
//Make a new index path for the 0th section, last row
let lastRow = tableView.numberOfRows(inSection: 0)
let indexPath = IndexPath(row: lastRow, section: 0)
//insert this new row into the table
tableView.insertRows(at: [indexPath], with: .automatic)*/
//Create a new item and add it to the store
let newItem = itemStore.createItem()
var Num: Int = 0
if let index = itemStore.allItems.index(of: newItem) {
switch newItem.room {
case "Bathroom":
Num = 0
print("I am in here")
case "Bedroom":
Num = 1
case "Dining Room":
Num = 2
case "Garage":
Num = 3
case "Kitchen":
Num = 4
case "Living Room":
Num = 5
default:
Num = 0
print("I am in Default")
}
let indexPath = IndexPath(row: index, section: Num)
tableView.insertRows(at: [indexPath], with: .automatic)
}
override func numberOfSections(in tableView: UITableView) -> Int {
return sections.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return itemStore.allItems.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//create an instance of UITableViewCell, with default appearance
//let cell = UITableViewCell(style: .value, reuseIdentifier: "UITableViewCell")
//get a new or recycled cell
//if indexPath.row < itemStore.allItems.count {
let cell = tableView.dequeueReusableCell(withIdentifier: "ItemCell", for: indexPath) as! ItemCell
//Set the text on the cell with the decription of the item
//that is at the nth index of items, where n = row this cell
//will appear in on the table view
let item = itemStore.allItems[indexPath.row]
if item.name == "" {
cell.nameLabel.text = "Name"
} else {
cell.nameLabel.text = item.name
}
if item.serialNumber == nil {
cell.serialNumberLabel.text = "Serial Number"
} else {
cell.serialNumberLabel.text = item.serialNumber
}
cell.valueLabel.text = "$\(item.valueInDollars)"
cell.roomLabel.text = item.room
if item.valueInDollars < 50 {
cell.valueLabel.textColor = UIColor.green
}else if item.valueInDollars >= 50 {
cell.valueLabel.textColor = UIColor.red
}
cell.backgroundColor = UIColor.clear
return cell
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
let LocationName = sections[section]
return LocationName
}
thank you so much for your time!
and this is how I create item
#discardableResult func createItem() -> Item {
let newItem = Item(random: false)
allItems.append(newItem)
return newItem
}
The problem is that you should insert the item in the array first:
itemStore.allItems.append(newItem)
Also, there is a difference between sections and rows in numberOfRowsInSection(return number of rows for every section) you have a switch that returns the same number, it should be
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return itemStore.allItems.count
}
Edit :
The problem is when the table loads there is 0 rows for all the sections ( itemStore.allItems.count is zero ), when you try to insert a row say at section 0 , row 0 -- the dataSource must be updated only for that section , which is not happen in your case as it's the same array that is returned for all sections , so you must either have an array of array where inner array represent number of rows so addition/deletion from it doesn't affect other ones ,,,,, or lock the insert to say section 0 like this
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (section == 0 ) {
return itemStore.allItems.count
}
return 0
}
in this edit i inserted in 2 sections 0 and 2 with no crash because i handled numberOfRowsInSection to return old numbers for old section that why to be able to insert in all sections you must have a different data source array or manage from numberOfRowsInSection , see edited demo here Homepwner
Instead of setting footer in viewDidLoad implement this method
override func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
if(section == 5 ) {
let textLabel = UILabel()
textLabel.text = "No more Item"
return textLabel
}
return nil
}
There is something terribly wrong with your data source. Your numberOfRow for all sections is the same. i.e. itemStore.allItems.count. Which means you are setting the same number of rows in each section.
The main issue is, while adding a new item, you are inserting a new row in the specific section which means
new number of rows for section = previous number of rows in section +
1
However, there is no addition in the data source.
So according to the code, you have inserted a new row, but your data source has one record less since you didn't add the item there.
So I would like you do the following in order of these steps :
Add item in required data source.
inserRowInSection
Update : Remove the condition in cellForRowAtIndexPath :
if indexPath.section <= 1 and it's else block. You don't need that. If number of sections is less than 1, it won't be called. Moreover it's the else block which is getting called all the time because you already have sections in your code. So if case will never be called.
So I got the issue. In itemStore.createItem() the value for room is always Room. In the switch-case, you never have Room case. So it always falls in default case. You need to fix that.
#Jacky.S I downloads your code and try to debug it.Fist look our error properly.
reason: 'Invalid update: invalid number of rows in section 1. 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 (0)
So It says before update, your sections has 0 rows. Now you try to update you tableview using this piece of code.
print(indexPath)
tableView.insertRows(at: [indexPath], with: .automatic)
I just added the print statement for debugging purpose and it prints [0, 0] .So you say your tableview to insert 0th row on section 0.
Now when you insert any row into tableview it calls the delegate method.Now look at the below delegate method.
override func tableView(_ tableView: UITableView, numberOfRowsInSection
section: Int) -> Int {
if section == 0{
print("0",itemStore.allItems.count)
}
if section == 1{
print("1",itemStore.allItems.count)
}
// print(itemStore.allItems)
return itemStore.allItems.count
}
In your code you just return itemStore.allItems.count.I added the above code for debugging.Now look that itemStore.allItems.count always return 1.So for first section it works perfectly because few times ago you insert a row in first section whose index is [0 = row, 0 = section].So you previously returned rows for section 0 is equal to the recently return rows for section 0.But When it comes for section 1 you previously return no row that means your section 1 has no rows in previous but in your above delegate method you also return 1 row for section 1.Which is conflicting because in previous you never update or return any rows for section 1.That's why you code crash and and say that
your number of rows contained in an existing section after the update is 1 which must be equal to the number of rows contained in that section before the update. Which is 0.
So this is the explanation for you crash.
Now, to recover from crash your allItems should be a dictionary or an array of array.
var allItems = [[Item]]()
or
var allItems = ["String":[Item]]()
guess allItems element is something like this
allItems = ["bathroom":[item1, item2, item3],"bedroom":[item1, item2, item3, item4]]
so here you have 2 section one is bathroom and another is bedroom.First section have 3 row and second one have 4 row and in you tableview delegate method you have to return 3 for section 0 and 4 for section 1
I have a collectionView that has two sections, each filling up cells with separate arrays.
override func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
if section == 0 && GlobalList.priorityArray.count != 0 {
return GlobalList.priorityArray.count
} else if section == 1 && GlobalList.listItemArray.count != 0 {
return GlobalList.listItemArray.count
} else {
return 0
}
}
override func numberOfSections(in collectionView: UICollectionView) -> Int {
return sections
}
I can move items between sections without any issues. I also can delete items. My problem occurs when I move an item between sections, reload data, then try to delete all the items in that section, I get the following error.
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of items in section 0. The number of items contained in an existing section after the update (3) must be equal to the number of items contained in that section before the update (3), plus or minus the number of items inserted or deleted from that section (0 inserted, 1 deleted) and plus or minus the number of items moved into or out of that section (0 moved in, 0 moved out).'
Here are the methods I have created to deal with deleting and moving items
Moving Items:
override func collectionView(_ collectionView: UICollectionView,
moveItemAt sourceIndexPath: IndexPath,
to destinationIndexPath: IndexPath) {
if (sourceIndexPath as NSIndexPath).section == 0 {
listItem = GlobalList.priorityArray.remove(at: sourceIndexPath.item)
} else if (sourceIndexPath as NSIndexPath).section == 1 {
listItem = GlobalList.listItemArray.remove(at: sourceIndexPath.item)
}
if (destinationIndexPath as NSIndexPath).section == 0 {
GlobalList.priorityArray.insert(listItem, at: destinationIndexPath.item)
} else if (destinationIndexPath as NSIndexPath).section == 1 {
GlobalList.listItemArray.insert(listItem, at: destinationIndexPath.item)
}
timer = Timer.scheduledTimer(timeInterval: 1.0, target: self, selector: #selector(reloadData), userInfo: nil, repeats: false)
}
Deleting Items:
func deleteItem(sender: UIButton) {
let point : CGPoint = sender.convert(.zero, to: collectionView)
let i = collectionView!.indexPathForItem(at: point)
print("deleting.. \(String(describing: i))")
let index = i
GlobalList.listItemArray.remove(at: (index?.row)!)
deleteItemCell(indexPath: i!)
}
func deleteItemCell(indexPath: IndexPath) {
collectionView?.performBatchUpdates({
self.collectionView?.deleteItems(at: [indexPath])
}, completion: nil)
}
Let me know if anything else is needed to figure this out.
Thanks in advance!
I also had a similar issue with the collectionView when calling reloadData and then directly after trying to insert cells into the collectionView. I solved this issue by instead manually deleting and inserting sections in the collectionView inside a perform batch updates, like so:
collectionView.performBatchUpdates({ [weak self] () in
self?.collectionView.deleteSections([0])
self?.collectionView.insertSections([0])
}, completion: nil)
While using swift UICollectionView with FlowLayout whenever I am trying to insert to delete cell (having 2 supplementary header section )it is giving below error :
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of items in section 1. The number of items contained in an existing section after the update (1) must be equal to the number of items contained in that section before the update (0), plus or minus the number of items inserted or deleted from that section (0 inserted, 0 deleted) and plus or minus the number of items moved into or out of that section (0 moved in, 0 moved out).'
Initially my UIcollection view has just two sections on display.
Then on user action I am adding items under section 0 or section 1 which depends on items category.
Whenever I make section as 1 insertItemsAtIndexPaths & deleteItemsAtIndexPaths are working fine. I am using IOS9 and swift . Have seen lots of post and tried things but have been stuck here.
var selectedPlayerDispCollection = [(String, PlayersProfile)]
func numberOfSectionsInCollectionView(collectionView:UICollectionView!) -> Int {
return 2
}
func collectionView(collectionView: UICollectionView!,numberOfItemsInSection section: Int) -> Int {
return selectedPlayerDispCollection.count
}
func performBatchUpdates(updates: (() -> Void)?,
completion: ((Bool) -> Void)?){
self.collectionView.reloadData()
}
**// For now adding in section 0 , depending on condition will add in section 0 or section 1
func addPlayerView(playertapped : PlayersProfile) {
let count = selectedPlayerDispCollection.count
//let index = count > 0 ? count - 1 : count
let index = count
let playerDisSec = [(playertapped.playerSpeciality, playertapped)]
selectedPlayerDispCollection.insert(playerDisSec, atIndex: index)
let indexPath = NSIndexPath(forRow: index, inSection: 0)
collectionView.insertItemsAtIndexPaths([indexPath])
}**
/*func addPlayerView(playertapped : PlayersProfile) {
let count = selectedPlayerDispCollection.count
let index = count > 0 ? count - 1 : count
selectedPlayerDispCollection.insert(playertapped, atIndex: index)
let indexPath = NSIndexPath(forRow: index, inSection: 0)
collectionView.insertItemsAtIndexPaths([indexPath])
}*/
As my comment already said: The problem is that you use one array to determine the number of items in both sections. Therefore removing one item from the array causes the collectionview to "lose" two cells. That losing of two cells does not add up with what you told the collectionView: remove one cell at indexPath abc.
Solution 1
Use two separate arrays as backing for each section or one array with sub-arrays.
Solution 2
Tell the collectionview to delete/add two items:
if let index = selectedPlayerDispCollection.indexOf(playertapped){
let indexPath0 = NSIndexPath(forItem: index, inSection: 0)
let indexPath1 = NSIndexPath(forItem: index, inSection: 1)
selectedPlayerDispCollection.removeAtIndex(index)
collectionView.deleteItemsAtIndexPaths([indexPath0, indexPath1])
}
You have to use the same logic when adding new items.