viewForHeaderInSection on insertSections is called for visible section - ios

Hello i have a weird edge case on UItableview when a new section is inserted.
What I expect: I insert a new section at 0 and viewFor/willDisplayed is called for that newly inserted section only.
What Im getting: I get the desired behaviour in all cases were current section count is 2 > N. But if there is currently only one section in the table and i insert a new one. viewFor/willDisplayed will be called on both 0 and 1.
This severely messes up the ui animations as I on insert-completion, update the content of section 1 (without reloading it explicitly) and call beginUpdate/endUpdate to update its height. But in this edge case this will now be called after its already got its new model set without any animations though viewForHeader resulting in a glitchy animation.
Anyone have any insight on how to overcome this Feture/bug of uitableview?

Related

The table-view moving up and down like jumping continuously

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.

UICollectionView insertItemsAtIndexPaths: seems to reload collectionview or autoscrolls to top

I am using a UICollectionView in combination with a NSFetchedResultsController. When I fetch new data, I throw them in a SQLite database and my frc gets a notification that it will update. I implement the appropriate delegate methods and insert the new data in a batch update and I don't call reloadData afterwards. But as soon as the data is inserted, the collectionview scrolls to the top (to make the new items visible?). I would like to interfere this process and imitate the Facebook/9Gag implementation. When there is new data, a button is faded in which lets the user scroll to the top. At the time the button is shown, the new items are already inserted, so the user can also just scroll up himself. I tried the following:
hooking into the UIScrollViewDelegate methods to see if I could stop the collectionView from scrolling (none of the methods is called during this update process)
show the "scroll to show more"-button when the frc willUpdate and just insert the items when the button is pressed. The problem is that the user cannot scroll up himself
Before inserting the items, I calculate the indexPath of currently visible items AFTER the insertion (adding the number of inserted items) and scroll to that index without animation in the completion block of the batch update. This only works if I delay the scrolling, but then it flickers, because it's first scrolling up a little
I experimented the same problem, by calling "insertItemAtIndexPaths" to increment data into my collectionView.
I noticed two things :
- If you add items AFTER the current visible index path, there's no automatic scroll
- If you add items BEFORE the current visible index path, there's no automatic scroll, like you said, but the contentOffset is kept at the same position, but with different contentSize, because you've just added new items.
To deal with this "side effect", I used the solution mentioned here : UICollectionView insert cells above maintaining position (like Messages.app)
As it's explained in the following post, the "CATransaction.setDisableAction(false)" is the key thing to update smoothly the contentOffset position.

Infinite UITableView Scroll with NSFetchedResultsController

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:.

very strange behavior with UITableView

I have a simple tableView where the value in numberOfRowsInSection depends on the type of data I am displaying, ie, names or details, etc..
The table is first loaded with certain details where the row count is 6. I added an NSLog statement and indeed I am returning 6 rows.
I also have to set the row height and again, my log statements says it is being called 6 times. great. My problem is cellForRowAtIndexPath is only called 5 times. it's missing the last call.
Note: I have a toolBar that reloads the table with different options. When I select a new option then go back, the table populates properly. It has 6 rows as it should have.
Everything is connected properly: delegate, datasource and outlet as proven by the fact that the table is being partially loaded. Does anyone have any idea why this is happening or how to fix it?
If your last cell is not visible on the screen the method cellForRowAtIndexPath won't be called if you don't scroll and make the cell visible.

Interesting issue with UICollectionView reloadData

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.

Resources