tableView reloadSections and reloadData white space and cells move to wrong section - ios

The issue is: When I execute reloadSections, the last cell of one section will jump to the first cell of the next section, even though cellForRowAtPathIndex definetly does not load this cell there (thouogh I did check and it is dequeing that cell, then updating it), and there will be an empty white space (sometimes) where a cell should be in the previous section. If I run reloadData, my whole table goes white until I scroll aroud a bit at which point it sometimes works. Background information:
I have a table view, which is displaying data with several rows per section. In the backgroud, a download may finish, which may or may not update the number of rows in each section and the data contained in the rows. The rows are varying heights, which is expensive for a "reloadData" call, so I don't want to execute that everytime new data arrives (only one section may be updated).
So, what I do when I start displaying a cell is check if a download for that section has been completed, and if it has I then reload that section. If that section is updated, I check the other sections as well (Since it often downloads sections together), and update all those.
Okay, here is the issue. I run something like this (abbreviated for simplicity / at work don't have exact code with me):
[tableView beginUpdates];
for (int i = 0; i < numSections; i++)
{
if ([self sectionNeedsUpdate:i])
{
[self updateNumberOfRowsInSection:i]; //if Needed
[tableView reloadSections:[NSArray arrayWithObject:i] withRowAnimation:UITableViewRowAnimationNon];
}
}
[tableView endUpdates];
The idea is that I update each section individually, but all 5 may be updated in that loop. Or maybe just section 1 / 3, etc. Why would a cell just jump to the next section? Are those reloadSections calls running in their own thread and dequeing each other at the same time?

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.

iOS : How to reload a UITableView with a lot of cells without lagging the App?

I have a lot of cells (around 3000 cells) that I need to reload constantly. I was wondering if there is currently a way to reload it faster without it lagging the App. I do the typical [tableview reloadData]; Any tips or suggestions are appreciated.
Don't implement tableView:heightForRow: in your delegate or it will slow down considerably as it recalculates every row. iOS checks to see if you implement that method and if you define it the OS changes its table height calculation from a simple multiply to a loop over the cells.
Since you have not provided context or code to show where you call [tableview reloadData];, I can only talk in generalities.
I am going to assume in your 3,000 rows possible 20 are displayed at a time.
Here is the sequence of events or actions that needs to occurs
A row gets update
Check if row is visible: indexPathForVisibleRows
If row is not visible, nothing to do
If row is visible, then following actions should be taken
[tableview beginUpdates]
[tableview reloadRowsAtIndexPaths...]
[tableview endUpdates]
Reload only visible cells if you have consistent number of items, otherwise use insert/delete methods to add cells to tableview.
When making a reloadData for your tableView, tableView:heightForRow: delegate function make a height recalculation of every row.
My solution is to save the heights for your cells already calculated (create an NSDictionary that contains all row heights. exp. create a NSDictionary with keys is the id of object that will be show on the cell and the value is the height).
When tableView attempt to recalculate the height of each cell, it will check if we have already a saved entry in your dictionary with this key (id of object), and tableView:heightForRow: will return this value if found.
I am using this solution in my chat app, and I noticed a performance increase.
Good luck.

UITableView's reloadRowsAtIndexPaths: seems to break insertRowsAtIndexPaths:

I have a UITableView into which the user can insert new rows. When this happens, I want to reload all of the old rows in the table. One solution would be to just call reloadData as soon as the insertion takes place, which totally works, but this means I don't get the insertion animation.
So when the user hits the "add row" button, I call reloadRowsAtIndexPaths: with every index path except the one just inserted. Then I call insertRowsAtIndexPaths: with only the newly inserted row. Reasonable, right?
This causes the app to crash with the following explanation:
Invalid update: invalid number of rows in section 0. 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), 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).
This happens, as you can see in this example, even when reloadRowsAtIndexPaths is passed an empty array of index paths.
Ah! I need to wrap the two calls with beginUpdates and endUpdates. Fair enough. But now the animation is completely broken.
I'm performing the reload with a UITableViewRowAnimationFade and the insertion with a UITableViewRowAnimationAutomatic. But during the animation, the heights of every row changes, creating this weird flickery effect that looks just terrible. What's the correct way to animate these changes?
Edit:
From the docs for reloadRowsAtIndexPaths:withRowAnimation::
Reloading a row causes the table view to ask its data source for a new cell for that row. The table animates that new cell in as it animates the old row out. Call this method if you want to alert the user that the value of a cell is changing. If, however, notifying the user is not important—that is, you just want to change the value that a cell is displaying—you can get the cell for a particular row and set its new value.
I think that, in my application, manually updating each cell is the right way to go. However, I am still perturbed by this bizarre animation bug, and would like to know what the cause of it is / what I would do if I did "want to alert the user that the value of the cell is changing."
A common reason for getting that error is, as the docs say, the datasource is asked for a cell. It might be less clear that it is asked twice...once to provide the initial data, again to provide the final data. That implies that the tableView:numberOfRowsInSection: method must return the old values before beginUpdates and the new values after endUpdates (or if you're using a shortcut method, before and after the call). Don't forget about numberOfSectionsInTableView either if it is relevant.
Example:
numberOfRows... return [array count];
// Incorrect
[array addObject:object];
[tableView beginUpdates];
[tableView insertRow..];
[tableView endUpdates];
// Correct
[tableView beginUpdates];
[array addObject:object];
[tableView insertRow..];
[tableView endUpdates];
For your specific case, I would recommend doing the insertion, then calling reloadData on the whole tableview so long as that doesn't mess up your animation or anything else.
You don't need to have any operations with exactly cells. All what you need - is change your datasource array, then call reload data/row/section, and you'll get changed data in your table view, with added rows.

UITableView reloadRowsAtIndexPaths performing animation when it shouldn't

I'm using a UITableView in my iOS app, and have been seeing a strange issue recently.
Suppose my table is structured as follows:
section 1 header
row
section 2 header
section 3 header
row
row
...
(Note that section 2 has no rows)
I'm performing updates to the rows in my table via
[self.tv beginUpdates];
[self.tv reloadRowsAtIndexPaths:ip withRowAnimation:UITableViewRowAnimationNone];
[self.tv endUpdates];
I don't want any animations taking place. I just want the row to update. The issue is that this strategy works for every row and section in the my table except section 3, row 1: the first row of the last section. When I update this row (which is indeed using the correct indexPaths), rather than get no animation, the row does this little jump, like it's sliding in a new row to replace the old one or something. The row slides up ever so slightly, then back down, as if I was inserting a row. I'm guessing it has something to do with the header calculations, but I do return correct values for heightForHeaderInSection.
Has anyone seen this behavior?
I wonder if the beginUpdates and endUpdates are necessary in this reload only scenario.
I had the same problem. The solution was to fetch the cell from the table using:
UITableViewCell* cell = [self.tableView cellForRowAtIndexPath:indexPath]
and then refresh it manually using a custom setup method or by simply calling:
[cell setNeedsLayout]
For more info, see:
UITableView reloadRowsAtIndexPaths graphical glitch

Graphical glitches when adding cells and scrolling with UITableView

I am using a UITableView to display the results of a series of calculations. When the user hits 'calculate', I add the latest result to the screen. When I add a new cell, the UITableViewCell object is added to an array (which is indexed by tableView:cellForRowAtIndexPath:), and then I use the following code to add this new row to what is displayed on the screen:
[thisView beginUpdates];
[thisView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation: UITableViewRowAnimationFade];
[thisView endUpdates];
This results in the new cell being displayed. However, I then want to immediately scroll the screen down so that the new cell is the lowermost cell on-screen. I use the following code:
[thisView scrollToRowAtIndexPath:newIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
This almost works great. However, the first time a cell is added and scrolled to, it appears onscreen only briefly before vanishing. The view scrolls down to the correct place, but the cell is not there. Scrolling the view by hand until this invisible new cell's position is offscreen, then back again, causes the cell to appear - after which it behaves normally. This only happens the first time a cell is added; subsequent cells don't have this problem. It also happens regardless of the combination of scrollToRowAtIndexPath and insertRowsAtIndexPath animation settings.
EDIT:
I've now started inserting cells at the second-to-last position of the table, rather than the end, and the problem still occurs - when first inserted, a cell is 'invisible' until it goes offscreen and comes back on again. What could be causing this, and how can I force the cell to be drawn as soon as it is added to the table?
You're having problems because your updating the table without updating the data model backing it. Tables don't actually know how many rows they have nor what cells to display. They depend on the datasource and the delegate to tell them these things. Your design expects the table itself to track them.
insertRowsAtIndexPaths: is intended to be used for moving existing rows around a table, not for adding entirely new logical rows. When you insert an entirely new cell, the tableview looses track of how many rows it actually has.
Before you display a new row, the first thing you should do is update the values returned by:
– numberOfSectionsInTableView:
– tableView:numberOfRowsInSection:
... to reflect the addition of the new rows. This will allow the table to understand how big it is.
Then you need to update cellForRowAtIndexPath: to return the correct cell for the added row. Then you need to reload the table.
After you've done that, you should be able to scroll the tableview to the end and have the cell display properly.
The important thing to remember about tables is that they are dumb. The table itself holds no data, doesn't know how many sections and rows it has or what order the rows and sections come in. All the logic about data, sections, rows, cells and cell contents comes from the datasource and/or the delegate. When you want to change a table, you actually change the datasource and/or the delegate and then the table will reflect those changes automatically.
Edit:
Upon rereading the parent, I see that your putting the actual UITableViewCell objects in your data array and that you have one cell for each row.
This is not how tableviews are supposed to work and this will not scale beyond a few dozen rows at most.
Tableviews are intended to be an illusion that allows you display a lOGICAL table which has an arbitrary high number or rows. To that end, it only keeps enough UITableViewCell objects alive to cover the visually displayed area in the UI. With a default cell height of 44 pixels this means a tableview will never have more than 9 cell objects at a time.
Instead of eating memory holding cells that are not displayed, the tableview lets the delegate dequeue a cell that has scrolled off screen, repopulate it with the data of another LOGICAL row and then display it in a new position. This is done in cellForRowAtIndexPath:
You really need to start over here with your design. Your data needs to be kept separate from the user interface objects. You don't want to have more cells alive at anyone time than absolutely necessary because your memory use will balloon and your response time will degrade. Your current problem is the result of this unusual design.
When you've done that, you can add the result row as outlined above.
Try to scroll with some time shift after cell update via NSTimer or performSelector:withDelay:. It can help but to fix all problems I think there need to do more work.
The glitches may be caused because a UITableView considers itself the owner of any UITableViewCell instances it is displaying, and reuses them as needed. Part of that process is calling prepareForReuse on the cell. Since you are keeping the cells in an array, you do not want them reused. Try implementing an empty prepareForReuse in your UITableViewCell class. Or just create cells dynamically in tableView:cellForRowAtIndexPath: as apple recommends.
I used what Skie suggested to avoid the problem in the following way:
Immediately after adding the row:
[self performSelector:#selector(scrollToDesiredArea:) withObject:newIndexPath afterDelay:0.4f];
This called the following:
-(void)scrollToDesiredArea:(NSIndexPath *)indexPath{
UITableView *thisView = (UITableView*)self.view;
[thisView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
The delay of 0.4s seems to be sufficient to avoid the glitching; any less and it still happens. It may have to be different on varying models of iPhone hardware, though - I only tested on emulator.

Resources