Inserting the first cell into a collection view causes an assertion failure - ios

If I try to insert an item into a UICollectionView when there are 0 sections and 0 items, I get an assertion failure.
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid
number of sections. The number of sections contained in the collection
view after the update (1) must be equal to the number of sections
contained in the collection view before the update (0), plus or minus
the number of sections inserted or deleted (0 inserted, 0 deleted).'
A classic. The problem is, I'm modifying my data source and then inserting the item directly afterwards. The showFirstCell property is checked in all of the necessary data source methods and it isn't modified anywhere else.
self.showFirstCell = true
self.collectionView.insertItemsAtIndexPaths([NSIndexPath(forItem: 0, inSection: 0)])
Wrapping this in performBatchUpdates changes nothing.
I would like for this item to be added in an animated fashion. I don't think I should have to first check if there are items already in place, but doing so and calling reloadData instead does work, but it's not perfect.

Your number of sections don't match up.
Once you have decided to enter a section to the UICollectionView, you should return the same number in your numberOfSections function as well.
What's happening right now is that you insert a cell, and it calls your numberOfSections function again, where you are still returning 0, and it promptly crashes.

The reason why it didn't work and gave you an error is because your numberOfSections is set to 0 and it didn't update yet when you add the cell.
To fix that, just create (if you don't have already) an NSMutableArray, and for numberofSections, return that mutableArray. Then whenever you add an object to the collectionView, just add an object to the mutableArray of sections.
Next and most important part is: Anytime you change, add, or remove something from the collectionView, just do a beginUpdat and endUpdate.

If you want to insert item from your CollectionView, you need to
insert that item from your data source which is in your array. In this example I inserted the item to myArray
then I inserted it also to myCollectionView. It's working because
the total number of items from your array (data source) is equal to
the number of items in the collectionView.
myArray.insert(valuetoinsert, atIndex: 0)
let path:NSIndexPath = NSIndexPath.init(row: 0, section: 0)
myCollectionView.insertItems(at: [path as IndexPath])

Related

Swift CollectionView Deletion Exception

I'm trying to delete a collectionView cell using this code :
myDataSourceArray.remove(at:index)
collectionView.performBatchUpdates({
collectionView.deleteItems(at: [indexPath])
}
, completion: nil)
its very straight forward deletion ,and after trying to delete any cell from section 1 for example , it causes this exception :
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 (2) must be equal to the number of items contained in that section before the update (2), 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).'
my collectionView distribution for cells is each section contains 2 cells like image below
and for last section if its containing 1 cell , it will contain 1 cell
func collectionView(_ collectionView: UICollectionView,
numberOfItemsInSection section: Int) -> Int {
if myDataSourceArray.count%2==1 && myDataSourceArray.count/2 == section {
return 1
}else{
return 2
}
}
What exactly causes this exception ?
Thanks in advance :)
Your problem is likely caused by the fact that you're not handling the deletion from the data source correctly.
The proper way to do it is to do the data source deletion inside of the update block, otherwise you can do the changes outside the update block but make sure the UI matches (reload it) before the update begins (which you did not).
Try moving myDataSourceArray.remove(at:index) inside the update block.
https://developer.apple.com/documentation/uikit/uicollectionview/1618045-performbatchupdates
The other thing which is probably not causing the issue but that you should be handling regardless (that I don't see you doing here), is sections have to be manually deleted. If your deletion operation results in the last cell of a section being deleted, you have to delete the section as well using deleteSections(:).

Crash on switching segment control

I have a tableview with 4 segments on top. At the 1st segment I have loaded 76 rows, at segment 3 I have 4 rows, at 4th segment I have about 25 rows & there's nothing at segment 2. If the rows in each of the segment are not scrolled down and if all of them are at the top(i.e. the first row is visible out of the total number of rows for each segment) then no matter how I switch the tabs, there won't be any crash.
But say for instance if at the 1st segment, I scrolled the rows down and then shifted to the 3rd segment, then there's a crash saying fatal error: Index out of range. Maybe due to mismatch in the number of rows on either tabs. But not sure about the fix. Hope somebody can help...
Please check that below things are satisfied in your case OR not ?
While Changing the Segment Control , you have to reload the tableView
with NewDataSource, and so you are going to change the UI so please
put the code of reloadData in main thread.
Make sure numberOfRows are maintained through Delegate method while
changing the segment Control.
And last but not least , another way to prevent the crash is that ,
you can check the indexPath.row < yourArray.count in
cellForRowAtIndexPath Delegate Method. This will surelly prevent the crash occures for index out of range.
From this case i came to understand you are using same UITableView for three segments also i came to know your call tableView.reloadData() when you change segment the crash happen because of one segement have more data and other don't have when scroll tableView. tableView's visble index positions not available in other segement's datasource because of that only you get crash and solution for this you need to scroll to the 0th index if count greater than 0 then reload tableView
when click on other segement before changing value to be populated scroll back to 0th index
if listArray.count > 0{
let index = IndexPath(row: 0, section: 0)
self.tableView.scrollToRow(at: index, at: .bottom, animated: false)
}
then change the value for clickedSegement to listArray
then reload tableview
self.tableView.reloadData()
Hope this will help you

Issue with deleting multiple cells in collection view at once

I'm allowing users to select/deselect cells in my collection view and then hit a save/delete button.
When I'm selecting rows, I add them to a dictionary
var selectedIndexes = Dictionary<IndexPath, String> ()
and when I deselect the row I set the selectedIndexes[indexPath] = nil
When the user hits delete I run
for index in selectedIndexes.keys {
indexesToDelete.append(index)
}
collectionView.deleteItems(at: indexesToDelete)
This goes into the selectedIndexes dictionary, grabs the indexes, adds those indexes into an array of indexes "indexesToDelete", and then after the forloop is over, I'm deleting that array of indexes.
When I run this I get:
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 (5) must be equal to the number of items contained in that section before the update (5), plus or minus the number of items inserted or deleted from that section (0 inserted, 2 deleted) and plus or minus the number of items moved into or out of that section (0 moved in, 0 moved out).'
I've printed everything out and the indexesToDelete are the correct indexes for what I'm trying to delete. I don't fully understand what the error message is saying.
What it is saying is that your collection view is no longer in sync with your data source. So you also need to update whatever you are using as your dataSource to reflect your changes and then call deleteCells.
for index in selectedIndexes.keys {
indexesToDelete.append(index)
}
yourCollectionViewDataSourceArray.remove(at: index)
collectionView.deleteItems(at: indexesToDelete)
if your collectionView data source is yourCollectionViewDataSourceArray, both are synced together. Every time collectionView reloads data it uses the the function with argument numberOfItemsInSection. When it finds out that your array.count is different than the total of items after deletion it gives you this error. Take it as a rule every time you want to update your collectionView by code, update the data array before updating it. Hope that helps

How to solve this CollectionView crash?

My app has two CollectionViewControllers. Only one is visible at a given time.
I have created the following structure on storyboard: two container views on top of each other. Every container view has a CollectionViewController embedded. The visibility of a particular container view determines which collectionViewController is visible.
This is the problem. Both CollectionViewControllers are receiving data in parallel but iOS has a bug that will make the app crash if one CollectionViewController tries to execute an insert using performBatchUpdates while it is invisible.
Trying to prevent that, I have created a BOOL flag on both CollectionViewControllers so they can know if they are visible and execute or not the performBatchUpdates. Something like:
if (self.isThisCollectionViewVisible == NO) return;
[self.collectionView performBatchUpdates:^{
// bla bla... perform insert,m remove...
This solves part of the problem. But the app continues to crash on the following condition: if I tap the button to switch to the invisible CollectionViewController making it visible while it is receiving updates.
I mean this: lets call A the first CollectionViewController and B the second one. A is visible and B is invisible at this point. B starts receiving data and is trying to do a performBatchUpdates but as it is invisible, the if (self.isThisCollectionViewVisible == NO) return; is preventing performBatchUpdates to run, what is fine. Now I make A invisible and B visible. At this point the flag self.isThisCollectionViewVisible is set to YES and performBatchUpdates makes the app crash with this error:
* Assertion failure in -[CollectionView _endItemAnimationsWithInvalidationContext:tentativelyForReordering:], /BuildRoot/Library/Caches/com.apple.xbs/Sources/UIKit/UIKit-3512.60.7/UICollectionView.m:4625
* 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 (76) must be equal to the number of
items contained in that section before the update (70), plus or minus
the number of items inserted or deleted from that section (5 inserted,
2 deleted) and plus or minus the number of items moved into or out of
that section (0 moved in, 0 moved out).'
I think the CollectionViewController is really not yet ready and updated to be able to do a performBatchUpdates... and this is not a matter of not updating the data source previously because it is being updated.
What checks can I do to prevent that from happening?
NOTE: I noticed something strange about this crash in particular. It says that 5 elements are being inserted and 2 deleted but in fact 3 elements are being inserted, 0 deleted and 2 changed when the crashes happen.
For me adding self.collectionView.numberOfItemsInSection(0) fixed the crash.
The collectionView has issues while inserting items when it is not visible.
Seems like I'm not alone with my solution: http://www.openradar.me/15262692
This crash told you that you didn't updated your datasource for collection. You need to update your dataSource (array or dictionary) and reload collection view data after you perform performBatchUpdates.
Invalid update: invalid number of items in section 0. The number of items contained in an existing section after the update (76) must be equal to the number of items contained in that section before the update (70), plus or minus the number of items inserted or deleted from that section (5 inserted, 2 deleted) and plus or minus the number of items moved into or out of that section (0 moved in, 0 moved out).'
As written in apple docs
Deletes are processed before inserts in batch operations. This means
the indexes for the deletions are processed relative to the indexes of
the collection view’s state before the batch operation, and the
indexes for the insertions are processed relative to the indexes of
the state after all the deletions in the batch operation.
So, move the changes before the inserts and it will the trick!
Encountered the same error today, for me, in performBatchUpdates block replace this:
NSArray *selectedItemsIndexPaths = [self.collectionView indexPathsForSelectedItems];
with this:
NSIndexPath *selectedIndexPath = [NSIndexPath indexPathForRow:self.selectIndex inSection:0];
NSArray *selectedItemsIndexPaths = #[selectedIndexPath];
Maintain the index by myself, it's OK now. The error should not be associated with data source, if you have had update the data source. It maybe related to the reuse of cells.

UITableView multiple insertSections to same index

I have a dataset that I want to display in an UITableView. All new sets of data I want to insert as a section 0. Often, I will get many sets, and then I want to push them on the top one at a time. This looks like
tableView.beginUpdates()
repeat {
data.append(dataProvider.nextDataSet())
tableView.insertSections(NSIndexSet(index:0), withRowAnimation: .Automatic)
} while dataProvider.hasMoreData
tableView.endUpdates()
While my data array is exactly like I would like it, my app crashes saying something like:
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 (5) must be equal to the number of sections contained in the table view before the update (0), plus or minus the number of sections inserted or deleted (2 inserted, 0 deleted).'
I have made a little sample project on GitHub that recreates this problem by doing
numSections += 1
tableView.insertSections(NSIndexSet(index:0), withRowAnimation: .Automatic)
five times (link) where numSections is the number of sections, and the number of rows is always three per section. Again it says that I have inserted only 2 sections, while in fact I did insert 5.
What is it I am misunderstanding here? Why does tableView think I only gave it 2 new sections? Of course, if I only give it 2 sections, it thinks I only gave it 1. :-I
The section index you insert doesn't matter as far as the order of your data being displayed is concerned, that's controlled by your data array. You should insert the sections at different indexes, so use the numSections variable to control that.
I expect if you move the begin and end updates into the loop it will also work. The problem is you don't know how the table view works internally, the issue you see isn't documented as far as I know, but it's illogical to insert sections at indices which don't match your underlying data.
The insertSections will be actually executed at the end of endUpdates(). So in actual scenario, the sections isn't inserted and you are specifying as the sections have been increased.
This will work. If you know the number of sections to be inserted, then use this method
tableView.beginUpdates()
numSections += 5
tableView.insertSections(NSIndexSet(indexesInRange: NSMakeRange(0, 5)), withRowAnimation: .Automatic)
tableView.endUpdates()
The above code is for the github code. It can be altered for your actual code. Do not call insertSections() in a loop or multiple times.

Resources