heightForRowAtIndexPath being called for all rows & how many rows in a UITableView before performance issues? - iphone

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.

Related

TableView height in Swift

My tableview add blank space to the end of the cells.
I don't know why, how do I fix this problem? (For ex I have 50 cells, two of them you can see here because this is last two cells)
Hard to know without seeing your code, but because your cells have varied heights, you should probably start by implementing tableView:heightForRowAtIndexPath:
If you end up wanting more manual control over the table's height, take a look at its contentSize property.

iPhone Dev - How to make a UITableView the exact height to fit all of the cells in it

I have the following:
I would like to make it so that the tableview stops right at the "So" cell, instead of having all the blank cells under it (the big plan is to have a bunch of multiple choice questions like the one in that pic, all on top of one another on one scrollview). I'm guessing I need to set the tableView's frame, but I was wondering if there was an easy way to calculate at runtime the exact height of the portion of the tableview where the cells are implemented (the ones with text in the pic). It's tricky because I made it so that each cell's height can change to accommodate the amount of text in it. Anyone have any advice?
There are two appproaches you can take.
Best and easiest.
1. Add a footer view to the table so it will not draw the rows after the last row. a blank footer view will do.
//Add empty view to hide trailing row seperators
UIView *emptyView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 320, 0)];
emptyView.backgroundColor = [UIColor whiteColor];
[tableView setTableFooterView:emptyView];
[emptyView release];
2.Check how many rows you have in the tableview and calculate the tableheight and change the frame of table view.
There isn't a tableView:sizeOfAllCells: function if that is what you are looking for. From your question it sounds like you to adjust the size of your table so you can put other content on bottom of it.
You are correct is saying that you want to alter your UITableView's frame property to match the height.
[table setFrame:CGRectMake(0,0,table.frame.size.width,heightOfCells)];
There are a few things you could do though. If you had an array of all the cells, you could loop through it real quick and add the height of each of them. You could (I don't particularly recommend it though) loop though the number of sections and rows you have, using tableView:cellForRowAtIndex: instead of having an array of cells, and get the heights that way. If each cell is the same height (sounds like its possible it wont be) you could just do some simple math to figure out the height. Lastly, you could keep a dictionary or array of heights and update it in the tableView:cellForRowAtIndex: so if the content changes, it updates the size, but then you would have to call a reloadData. Those are just a few ideas of ways to solve this particular problem.
Here's a hack (so wait to see if a better solution comes along):
Use numberOfSections to get the number of sections (if you later change to grouped style), and then add up the heights of rectForSection: for each of the sections (in plain style, just take rectForSection:0). Maybe add a bit to this for the separators.

iPhone - Custom TableViewCell height calculated off cell text

I'm creating a custom cell in the cellForRowAtIndex function and filling it with my dynamic text. The height of the cell is variable based upon this text so I need to user the heightForRowAt function, but in that function I don't have the cell. I can create a new one and fill it with the text and then call my calculateHeight function on it but then I'm creating 2 cells for each row.
Anyone have a better method? Cache the cells yourself in the cellForRow function in a dictionary by row index and use that? Is cellForRow called before heightForRow?
I've managed to crash Xcode4 twice so far playing around with this so am hoping someone has done this already.
tableView:heightForRowAtIndexPath:
is called before
tableView:cellForRowAtIndexPath:
Creating two cells is a possible solution, but creating two cells isn't a very effective method. However, you don't need the cell to calculate the height; All you need is the string.
You'll probably want to use
sizeWithFont:constrainedToSize:
on your string to calculate the height.
I could provide details on how to do that, but you should be able to figure it out from reading the documentation.
What you do is you make a class method on your custom cell (for instance), which knows the values of how things (statically sized things) will be set, and does some math to determine given the offsets of the text view for instance, it can tell you how big the cell needs to be after calculating how high your string will be given a font.

manual layout of a custom UITableViewCell for this approach?

Background - I have a custom UITableViewCell layout. Each cell will have a number of UILabels with a variable number of rows of UILabels. For the sake of argument conside 3 columns of UILabels (different widths), and a variable number of rows depending on the data.
To best allow for content view size changes (e.g. edit mode, change in orientation etc) I was going to manually layout each of the UILabels in the cell in the "layoutSubviews" method, effectively setting up their exact positions. I thought this way they will be laid out appropriately for events such as EDIT mode, orientation change etc.
Question - I'm wondering whether from a performance point this is the best approach? Or should I be looking at somehow predefine the UILabel rows, one predefined set for portrait mode and another set for landscape mode? (not exactly sure of how this would be done, but I'm trying to describe an approach where the layout would not have to be re-calculated when orientation changes etc)
thanks
suggestion by RickMaddy elsewhere was good:
"Why have a variable number of rows of labels in a single table row? Have one row of labels per table row. Then the problem is easier and performance won't be an issue."

UITableView Did Load (Done drawing the cells)

Question
How can you detect when the Table View is done drawing the cells?
Issue
I got two labels within the contentView of an UITableViewCell. The size of these labels are dynamic. I was able to do so by subclassing UITableViewCell, in the drawRect method I adjust the frames of the two labels depending on their content. Now I want to align all the second labels.
My Thoughts in Steps
Determine the content in the table view and let it load automatically.
Run through the table view cells and determine the x position of the second label within the UITableViewCell that is the furtherest away.
Store this x position and when any cell is drawn use this x position to place the second label.
The problem is that if I use the following code:
for (int row = 0; row < [self.tableView numberOfRowsInSection:section]; row++) {
UITableViewCustomCell *cell = (UITableViewCustomCell *)[self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:row inSection:0]];
NSLog ([cell.labelTwo description]);
}
The second label has not yet been drawn, meaning I can't determine the size of the frame and thus can not find the proper x position to align all second labels.
I have tried subclassing the UITableViewController and looking at events such as viewDidLoad and viewDidAppear unfortunatly also in these events the cells aren't drawn yet.
What I Want ...
What I want is for the table view to draw the cells at least once so I can determine the sizes of the labels within the table view cell. I thought to accomplish this by looping through all the cells with cellForRow, but although it successfully returns the cell the content is not drawn yet meaning the frame remains with a width of zero.
Does anyone have a solution?
Thanks in advance,
Mark
Try calling sizeWithFont: on the contents of these labels to get the max width before you draw anything. You should be able to use it later in your cellForRowAtIndexPath: to adjust the width as you need.
I would recommend you reconsider using UITableViewCellStyleValue2 cells instead and attempt to configure the textLabel and detailTextLabel. I had a similar situation and this is how I did it.
First off, you really ought to just pick an explicit, fixed position at which the first label ends and the second one begins, based on what you know about the minimum and maximum lengths of the text that will be put in those labels. That would eliminate this problem entirely.
But if you want a solution: use the sizeWithFont: method or one of its cousins (see the Xcode docs). Specifically, loop through the values that will go in the first labels, apply sizeWithFont to each, and keep track of the largest width you see. (I'm assuming you have access to the values before they go in the cells; since they're dynamic, they must be passing through the table view controller, no?)
Now you have the value you seek, without having to perform the extremely wasteful operation of creating a bunch of cell objects and never using them for their intended purpose.
I think what you need to do is to add a viewController to the have the UITableViewController control the UITableViewCell itself so that you can capture the events of the labels loading. The viewController will have references to both labels so it can adjust them accordingly in response to -viewDidAppear.
I've never done this but a UITableViewCell is a view like any other so you should be able to set up a controller for it. You might need to manually activate the controller since you have no navigation controller to do it for you in this context.