I'm trying to use this method to implement an infinitely scrolling UITableView
The core logic of the solution is:
To increase the tableview content by a factor of 3, so that we make the 3 copies of the content laid one after another vertically.
Whenever the top end of the scroll is reached, move the the scroll offset back to start of the 2nd copy
When the bottom end of the scroll is reached, we move the scroll offset back to the start of the 2nd copy minus the height of the tableview, so that we end up showing the same content as we are now.
This means that a single cell insert or delete actually results in three inserts or deletes. Because my table's datasource is populated by an NSFetchedResultsController it causes an Assertion Failure.
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 (15), 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).
Is there some way to stop the program from crashing in these cases? I'd really appreciate any help/pointers. Thanks.
This can be kind of tricky in iOS especially when you're first starting out. The key is to update the datasource that is backing your UITableView before you inform the tableview of changes.
For instance, if you have an NSArray backing your tableview, then you will want to remove or add items to it, prior to calling reloadSection: on the tableview.
Although it is the least optimized solution, while your testing feel free to simply call reloadData which will ignore what the tableview has cached and force it to recalculate based on whatever is backing the tableview.
You can use NSFetchedResultsControllerDelegate method controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:. To insert\delete 3 objects.
To keep it simple you could return 3 for number of sections in your table view (this will handle getting 3 copies of objects). And then in your NSFetchedResultsControllerDelegate update objects in all sections of your UITableView (insert, delete or move). You should use row number from the NSFetchedResultsControllerDelegate and apply all changes 3 times each for different section. Remember to keep it consistent with UITableView data source method tableView:numberOfRowsInSection:.
Related
I am working in a project which is in Objective C and Xcode version is 10.1. In this project I am using socket. In this app I am continuously reading data from Socket. And I am showing the updated value into table view. For that I am reloading the table section header as fast as data comes from socket. Means If I get id = 5 then I try to find index of section whose id == 5 and I will update that section only.
Now the problem is when I tap on table header it opens inner row of that section header and after that when I scroll down, the table-view moving up and down like jumping continuously because data is coming in such a fast way.
For Ex. : Table View
Case 1 :(Scroll is working perfectly)
Section Header 1 (When I click on Header 1, Row 1 will appear)
Section Header 2
Section Header 3
Section Header 4
Section Header 5
Case 2 : (Scroll will lead to Jumping Headers)
Section Header 1 (When I click on Header 1, Row 1 will be removed)
Row 1
Section Header 2
Section Header 3
Section Header 4
Section Header 5
So in above structure, when we click on Header 1 and then we scroll down, at that time next headers are jumping because of continuous update process.
This jumping header effect in scroll, is not coming in Case 1 scenario. This happened only in Case 2.
This problem was not occurring when I was reloading the whole table instead of reloading the only one section header at a time.
Please help me in this issue.
Thanking you in advance.
I tried to reload section header like mentioned in below.
[tableView beginUpdates];
[tableView reloadSections:[NSIndexSet indexSetWithIndex:i] withRowAnimation:UITableViewRowAnimationNone];
[tableView endUpdates];
If you are using "heightForRowAtIndexPath:" method to calculate the height. Use "estimatedHeightForRowAtIndexPath" this method also. Write code in row height method.
When you reload the section of the UITableView, it will try to do the whole render process again. That means, it will get the number of rows, number of sections, get the appropriate cell for that row or section, will calculate height of each one of them and then draw on screen.
This whole process takes a lot of time. Since all the UI operations happen on Main thread which is a serial thread, and you are updating the table view section continuously and very fast, this will cause a lot of jitter.
A better way to handle such a scenario is to delay the updates a little. Say in an interval of 1 min, take the latest update. This can be achieved using RxSwift or may be implement your own logic.
Then after you do that, an optimization that you can attempt is to update the section view yourself rather than letting UITableView doing it for you. This means, that based on the current state of the UITableView, find the section you want to update with the data, use indexPathsForVisibleRows and cellForRow(at indexPath: IndexPath) to get the cell and directly update the view.
This method will work fine if the new data does not change the height of the cell or section. Otherwise, you will have to invalidate the height of the cell which will introduce a little bit of jitter.
If you reload section, then everything to do with that section will be redrawn, including row numbers, header contents, and default section behaviour (closed or expanded). you need to change your design or your approach to the solution required - for example, make the table non-closable (ie header and childs are always visiblle), or other things.
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.
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.
I have a table view with each rows having image and some text. The images are loaded asyc. As soon as image is fetched from server, the delegate methods gets called. The delegate method contains the index path which initiated the image fetching, so that I can reload only those cells. I have extra check to make sure that once the image is fetched, the data source contains enough data so that index path doesn't go out of bounds. Despite setting this condition the app crashes.
There is a chance that my table data gets updated before the image is fetched. I know this is cause of the issue, but I am not sure why this is making a crash despite adding a check before
reloadRowsAtIndexPaths?
The error is:
Fatal Exception NSInternalInconsistencyException Invalid update: invalid number of rows in section 2. The number of rows contained in an existing section after the update (4) 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 (1 inserted, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).
Can some one share any inputs on this?
Code snippet that reloads the table
// this method will be called on main thread
- (void)loadImageAtIndexPath:(NSIndexPath *)inIndexPath {
if(inIndexPath.row < dataSource.count) {
[self.listView reloadRowsAtIndexPaths:[NSArray arrayWithObject:inIndexPath]
withRowAnimation:UITableViewRowAnimationNone];
}
}
This is driving me nuts, I have sections in my UITableView, but this insertion works when I don't have sections.
Basically I'm doing:
[self.tableView insertRowsAtIndexPaths:array withRowAnimation:UITableViewRowAnimationRight];
I'm getting this error:
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 (4), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted).'
I thought it would just work, but this doesn't make sense. What can you guys make of this?
You are returning different values from your numberOfSectionsInTableView: method before and after you send the insertRowsAtIndexPaths:withRowAnimation: method.
If you are creating an entirely new section in the table view, you must insert it by sending insertSections:withRowAnimation: message to the table view.
Whenever you make a insertRowsAtIndexPaths:withRowAnimation call, you also need to modify the data source that backs the table with a similar addition. This ensures that tableView:numberOfRowsInSection: returns n before the addition and n+1 after the addition. Anything besides a consistent result with throw the error you described.