I thought I had read that for a UITableView
that heightForRowAtIndexPath
doesn't get called on all rows, but only on the ones that will be visible. This isn't what I'm seeing however. I'm seeing hundreds of calls to heightForRowAtIndexPath
for the simple situation of the orientation being changed of the iPhone for example.
So I'm assuming here therefore that for a UITableView
with heightForRowAtIndexPath
implemented, it does (i.e. heightForRowAtIndexPath
) get called for all rows (not just the visible ones)...let me know if this isn't quite correct.
QUESTION: Given the above, how many rows in a UITableView
(where heightForRowAtIndexPath
is implemented) can you have before performance issues occur typically?
Is there a way around the performance issues? i.e. set a nominal/standard height for each row and not implement heightForRowAtIndexPath
, but then correctly set each row height only when it is displayed and set it correctly here...but which method would one do this in?
Have a look at the discussion section in the tableView:heightForRowAtIndexPath:
documentation
The method allows the delegate to specify rows with varying heights. If this method is implemented, the value it returns overrides the value specified for the rowHeight property of UITableView for the given row.
There are performance implications to using tableView:heightForRowAtIndexPath: instead of the rowHeight property. Every time a table view is displayed, it calls tableView:heightForRowAtIndexPath: on the delegate for each of its rows, which can result in a significant performance problem with table views having a large number of rows (approximately 1000 or more).
So you should use the rowHeight
property of the UITableView. If you need different heights you are out of luck because you have to use tableView:heightForRowAtIndexPath:
.
AFAIK there is no way to change the row height at display.
The tableview has to know the correct size before, otherewise there would be ugly position shifts all the time.
I think I found a solution to that.
In iOS 7 apple introduced some new tableview properties. One of them is the:
tableView:estimatedHeightForRowAtIndexPath:
So if you supply an estimated row height, for example, then when tableView:heightForRowAtIndexPath: is called repeatedly before the table is displayed, it is called only for the visible cells of the table; for the remaining cells, the estimated height is used.
Here is the source for that information: https://books.google.gr/books?id=wLaVBQAAQBAJ&pg=PT521&lpg=PT521&dq=heightforrowatindexpath+only+for+the+visible+cells&source=bl&ots=7tuwaMT5zV&sig=h3q8AaFvoCgcrPu2fQchVkIEjwg&hl=en&sa=X&ved=0CEMQ6AEwBWoVChMInLK0xbPuxwIVCbUaCh3_nQWG#v=onepage&q=heightforrowatindexpath%20only%20for%20the%20visible%20cells&f=false
My goodness, I spent over an hour trying to find the source of my performance problem!
Finally I also found hundreds of calls to heightForRowAtIndexPath and a search
got me this thread. THAT is really annoying.
Performance goes down here already when just displaying 250 items. Thankfully the cells I want to display now all have the same size. But I could imagine someone wanting to display some different cells for a tableView with > 200 items!
FIX THIS APPLE!
Cheers
A way to improve performance in tableViews with a big number of rows and dynamic cell heights is to cache the height of the cells once they are first calculated.
A simplistic approach to achieve this is to keep a NSMutableDictionary
in which the key is the id of the record in the cell (or any other identifier you might have), and the value is a NSNumber
with the height of the row. Once the height is first calculated, store it the NSMutableDictionary
by the record id. In the tableView:heightForRowAtIndexPath
and tableView:estimatedHeightForRowAtIndexPath:
you check for a cached height in the dictionary and return it if found. If not found, calculate the height, and store in the cache before returning the height.
You might have to be careful with invalidating the cache for the rows that change heights. For example, if you have an expand button in one of your cells, you will need to remove the height of that cell from cache once the expand button is tapped delegate method for height is called.
You might still have a performance hit if you try to display 1000 of cells at once when the table shows, as it will likely call the height method for each row. A work around for that is to first warm the cache, if possible in a background task, before first displaying the cells.