UITableView reloadRowsAtIndexPaths performing animation when it shouldn't - uitableview

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

Related

Strange tableView row/section animation when row is selected

This cool expanding tableView works great but when you select some of the rows, a weird animation occurs.
The table has multiple sections, and each row can show additional rows when clicked. Row appearance and behavior is structured in a plist using various values e.g. if a row's boolean "isExpandable" property in the plist is set to true and its "additionalRows" property is set to 2, the row can expand to show 2 sub-rows.
The issue with the table is that when the first cell in each section is clicked, the animation runs fine, but when other cells are clicked, they shuffle:-
I suspect the strange animation happens because all sections are reloaded when a cell is clicked because of this line under didSelectRowAtIndexPath in the ViewController file:-
tbl.reloadSections(NSIndexSet(index: indexPath.section), withRowAnimation: .Automatic)
I've tried the following but they either crash the app or cause the cells to not expand:-
Setting the animation method to .None
Using reloadData instead
Storing the indexPaths of the selectedRow and its sub-rows, and then reloading all with reloadIndexPaths
Storing the rows of the selected cell and its subcells then reloading them once when a row is clicked
Someone on the actual site that the code is posted on suggested using insertRowsAtIndexPath and deleteRowsAtIndexPath instead of reloadSection, but I'm not sure how to do it. Your help is greatly appreciated.
EDIT
- Also tried insertRowsAtIndexPath and deleteRowsAtIndexPath but it seems to conflict with the way the tableView model is set up and the app crashes.
Inserting rows into the table should help you with more consistent table view animation behaviour.
To insert rows at specific index paths you can use the 'insertRowsAtIndexPaths' method. You will need to generate each index path that you are inserting and then pass them to the method in an array.
ObjC:
NSArray* arr = your NSIndex Paths...;
[tableView insertRowsAtIndexPaths:arr withRowAnimation:UITableViewRowAnimationFade];
Swift:
table.insertRowsAtIndexPaths([IndexPaths..], withRowAnimation: .Automatic)
https://developer.apple.com/library/ios/documentation/UIKit/Reference/UITableView_Class/#//apple_ref/occ/instm/UITableView/insertRowsAtIndexPaths:withRowAnimation:
Here is the problem: reloadSections(..) makes the tableView conveniently fetch all of the newest data from the model (for the specified section(s)) at the cost of more 'vague' animation. Use insertRowsAtIndexPaths(..) with the .Top animation style for the behavior you are looking for. If you have issues with your Data Model, fix that first (because it will almost certainly bother you later).

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.

iOS UITableView behaviour broken when not active

My current set up is the following:
Root Tab Bar:
Collection view with magazines
Bookmarks (with a table view)
Others
You can add a bookmark from a magazine in the collection view and also remove it from there.
The behaviour I'm seeing is the following:
I start the application, the table view queries the number of sections, number of cells, but not the cellForRowAtIndexPath. I could understand why, as there is no cell in the active view, so no data should be loaded.
When I add a bookmark from the collection view, it adds it to the array (via a notification) and requests the tableview to be reloaded. As there isn't an initial entry, it goes through the motions described above. When I press it again to remove the bookmark the entry is removed from the array. This is where it gets interesting because the first thing the table calls is not the number of sections or rows but the cellForRowAtIndexPath. As the array is empty, the application crashes on a request for data on index 0.
My question is why does the cell creation get called in that order? Is there any way to avoid it?
If you changed the section, try calling - (void)reloadSections:(NSIndexSet *)sections before you call reloadData
https://developer.apple.com/library/ios/DOCUMENTATION/UIKit/Reference/UICollectionView_class/index.html#//apple_ref/occ/instm/UICollectionView/reloadSections:
[self.tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationLeft];
The reason this was happening is because I was attempting to change something about the table before I reloaded the data.
[self.tableView setSeparatorStyle: !_helpText.hidden ? UITableViewCellSeparatorStyleNone : UITableViewCellSeparatorStyleSingleLine];
[self.tableView reloadData];
That was for removing the lines so a message can be displayed. However that update was using old data as reloadData had not been called.
[self.tableView reloadData];
[self.tableView setSeparatorStyle: !_helpText.hidden ? UITableViewCellSeparatorStyleNone : UITableViewCellSeparatorStyleSingleLine];
Reversing them fixed the issue.

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

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?

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.

Resources