Interesting issue with UICollectionView reloadData - ios

When dealing with a UICollectionView in my app I've ran into a strange problem related to reloading data. After lot's of debugging and analyzing logs I've come to the conclusion that if reloadData is immediately followed by insertItemsAtIndexPaths the dreaded error below is guaranteed to occur:
Name: NSInternalInconsistencyException Reason: Invalid update: invalid
number of items in section 0. 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 (1), plus or minus
the number of items inserted or deleted from that section (1 inserted)
...
They only way for this to happen consistently is that internally the UICollectionView is still busy with reloadData when the call to insertItemsAtIndexPaths arrives. The fact that "collectionView:numberOfItemsInSection" is called twice in a row before the insertItemsAtIndexPaths completes seems to support this since that method is never called twice in a row in call other cases.
Has anyone seen similar behavior or can confirm my analysis or even suggest a proper workaround?
Update: Any yes I've made sure that all relevant invocations occur on the main thread.
Update 2: Since the reasoning behind getting into this situation at all has been questioned: I'm using Monotouch and the code in question is intended to keep generic .Net Collections firing this event into the appropriate calls to keep the UICollectionView bound to the collection in sync. When the source collection is cleared it reacts with a Reset action, followed by one or more Add actions when items get inserted into it which leads to the problem outlined above. Hope this helps.

When you call insertItemsAtIndexPaths (removeItemsAtIndexPaths is analogous), you are telling your collectionview that its datasource now has more items available, and that it should insert these available items at the indexpaths you specify.
It checks your datasource whether that statement is true, and if it detects that the amount of old items plus the amount of items you say you inserted is not equal to the amount of new items, it says it can't do the update, as you lied about how many items you changed.
Now, what you are doing is you are telling your collectionview is that it should reload all its data from its datasource (with the new data) and right after that, you tell it you inserted x items. That is a false statement, as you just reloaded the collectionview, which updated its amount of items, and thus the amount of items before the update is equal to the amount of items after the update (you're not doing anything), and not increased by the amount of indexpaths you specified.
I hope you're still with me, because here's your solution:
Remove the reloadData before the insertItemsAtIndexPaths, as this breaks its assertions and will throw exceptions when incorrectly used. If you would like to reload the collectionview before inserting items, make sure you perform insertItemsAtIndexPaths right after you change the items in the datasource.
Read up on the documentation for this method here.

Related

UITableView is crashing due to reload data at the same time as scrolling the tableview

I am using UITableView to show a list of records. My table allows refresh from top and bottom. On refreshing from top, the old records remain visible until new data is fetched. After the data is fetched, all the records from the array are removed and new records are filled. Then as a final step I refresh the tableview to reflect the newly fetched data.
All works like a charm.
BUT...
Its causing a crash when we do monkey testing. During the top refresh, when the data is being fetched, if I keep scrolling, pulling the tableview, after several attempts there comes an unlucky moment when array is being removed to add the new records but at that time UITableView is also laying out the cells due to constant pulling. This causes the crash.
An easy fix would be to empty the tableview when doing a top refresh but that don't look good as far as the ui aesthetic is concerned. Also if the call fails I won't have anything to show to the user.
I tried by encapsulating the refreshing code in tableview's beginUpdate and endUpdate block but that won't run as there is no change in the tableview itself (i.e. no adding/deleting).
Any help would be appreciated.
Since you say you scroll away and crashes is probably because you try to insert data to indexes that are no longer visible. You should somehow check at what index you are before trying to update the table.
Some code would help me better understand.

Insert and delete rows in UITableView before first appearance on screen

I perform a bunch of row/section inserts and deletions with insertSections, insertRows, deleteRows etc. during the startup of a view controller. The manipulations are triggered by events of a background process (of course the actual calls to the table view are performed on the main thread; also, they are wrapped in beginUpdates and endUpdates).
When I start off these row manipulations in viewWillAppear I eventually get a crash because of an inconsistency in the row data: attempt to delete row 1 from section 0 which only contains 1 rows before the update. When this happens, there actually are 2 rows in the section before the update which apparently is not correctly recognized by the table view.
However, when I start the exactly same sequence of manipulations in viewDidAppear, there's no crash and the rows animate in and out as expected.
This looks like the table view has problems with a fast-running sequence of inserts and deletes before it appears. Is this a known limitation, or do you have another explanation for this issue?
Looks like you dont need to manipulate actual UITableViewCells, you may just manipulate some kind of view models (plain objects), prepare them, and then reload UITableView with them on viewWillAppear

Swift - invalid update of collection view - multiple data changes

I have problem with my collection view and reloading/inserting items. I have my items array in view controller which contains UICollectionView. I change content of items array in many places. Sometimes I insert new items and sometimes I must reload items again from database.
Not all the time but sometimes I get "classic" error when inserting new items:
Invalid update: invalid number of items in section 0. The number of
items contained in an existing section after the update (16) must be
equal to the number of items contained in that section before the
update (16), plus or minus the number of items inserted or deleted from
that section (1 inserted, 0 deleted) and plus or minus the number of
items moved into or out of that section (0 moved in, 0 moved out).
I know meaning of this error but It's really hard to fix this because as I wrote I can get notifications and then I reload data immediately (get items from database and reload collection view) and it could happen when inserting new item and similar conflicts could happen.
So I am not sure what is best solution for this. Should I lock array and make it thread safe (but I think I edit it mostly in main thread) or should I somehow "lock" collection and when there is insert operation then there couldn't be changes to items or collection view couldn't be reloaded? Or is there other solution?
Bonus: Is it possible that app wouldn't crash when error above happens and just full reload collection view? Propably not but it could help a lot.

tableView still shows two rows even though I deleted one from the data source and reloaded the table

I have an NSMutableArray _conversationsArray that basically stores IDs of all the conversations a user has in my app. Based on that value I show different number of rows in the tableView. When I delete a conversation from the data source and trigger reload of the tableView, numberOfRowsInSection method triggers and by setting a breakpoint I check the number of elements (conversations) in that array:
Everything seems to be fine, the result is one object which is great because I had two of them before the deletion. The problem is, as you can see on the far left of the screen, both rows are still visible in the tableView (even after the breakpoint) :/ The second row should have disappeared but it didn't. It did become unresponsive to touch events, but I need it gone.
Have you ever experienced a problem like this? Any help would be greatly appreciated.
--- Update ---
Could it be that it has to do something with the threads? because it seems that the table updates as it should when the thread changes :/ I might be wrong about it as it's hard to debug
It looks like you're not reloading the table view on the main thread, since -tableView:numberOfRowsInSection is called on a background thread (?). In UIKit -reloadData must be called on the main thread.

NSFetchedResultsController conflict with number of changed records

I have an iPad app with ARC, storyboards, supporting ios5+.
I have a VC that is working with NSFetchedResultsController on its own.
The VC has two tableviews pointing at two different entities.
I have single record add, change, delete, and moving of rows working fine on this VC.
I've now introduced a new feature to enable the user to quick add multiple rows to these tableviews.
I do this by providing a segue (modal) from this VC to a new VC (lets call it quickAddVC).
The user enters the data on quickAddVC, and then I segue back (modal) to the original VC.
I save the new records (via an alternate call) before (I believe) setting up the NSFetchedResultsControllers for the VC again (I set them to nil in ViewDidUnload).
However I keep getting this mismatch error.
Somehow it keeps registering the number of inserts that occur from the quickAddVC as 1 insert no matter how many records I add, and it somehow remembers how many records were in the NSFetchedResultsControllers before my segues to do this comparison.
Error:
* Assertion failure in -[UITableView _endCellAnimationsWithContext:], /SourceCache/UIKit/UIKit-2372/UITableView.m:1070
Failure in didChangeObject, name=NSInternalInconsistencyException reason=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 (2), 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 ideas on what I am doing wrong or should try would be useful.
I can't figure out how to get the NSFetchedResultsControllers to simply forget its original state and restart from scratch which would I think resolve this issue for me.
I haven't posted any of the code, I guess I could post a lot if needed but hoping there is enough here to outline the design issue and get some ideas.
Just reposting the answer to mark question as closed.
Ok solved the issue. Basically I was clearing the NSFetchedResultsControllers in the wrong place. By setting them to nil just prior to the segue all works fine. I'd made an incorrect assumption that given the ViewDidLoad method was being called when I segued back again, that the ViewDidUnload method was also being called but that was not the case.

Resources