In setting up your UITableView use to self-sizing cell, you have to specify estimatedRowHeight. Why is this even needed - given that the cell height will be calculated first by the system before the cell is displayed?
Setting the row height to UITableViewAutomaticDimension (-1), tells the auto layout engine that it needs to solve for the row height. The estimated height gives the engine a starting guess for solving the layout constraint equations.
From the documentation for the estimatedRowHeight:
Providing a nonnegative estimate of the height of rows can improve the performance of loading the table view. If the table contains variable height rows, it might be expensive to calculate all their heights when the table loads. Using estimation allows you to defer some of the cost of geometry calculation from load time to scrolling time.
When you create a self-sizing table view cell, you need to set this property and use constraints to define the cell’s size.
Related
I'm working on an iOS App right now and I want to build a view controller that uses a UITableView to create new events in a calendar (very similarly to how iOS handles event creation in the system calendar, actually). The table view has two sections, the first section holding a date picker and the second section holding two custom cells for entering an event name and notes via a text field and a text view. After playing around with them I managed to force-set them to the right size, but in the process I realized that I don't actually understand how iOS calculates individual cell heights, especially in a table view with multiple sections and multiple custom cell classes. So far, I've found a number of things that seem to play a role:
Contents of a cell, e.g. a text field and its constraints
Hugging priority and compression resistance priority of a cells content
Settings for row height and view height in the size inspector of the cell itself:
Arrangement and Autolayout settings in the size inspector of the cell
Settings for the rowHeight and estimatedRowHeight properties of a UITableViewController
The more I look into it, the more complex and confusing it all gets. Maybe one of you can shed some light on this shady bit of Swift magic?
Basically, the rule is that if the table view's rowHeight is UITableView.automaticDimension, then as long as the estimatedRowHeight isn't 0, you'll get automatic row heights, meaning that the height is determined by the cell's autolayout constraints from the inside out.
The settings can be made in respect to the table view as a whole (in code or in the storyboard) or for a single cell using the height delegate method.
Add your constraints in the cell in right way.
don't use tableview "height for cell" delegate method.
use this in your viewDidLoad
self.tableView.estimatedRowHeight = 44.0
self.tableView.rowHeight = UITableView.automaticDimension
I would say that table view has a bit tricky.
Originally it needed to know size of cell before the cell was created.
The height of cell is defined by UITableViewDelegate optional function tableView(_:heightForRowAt:)
If this function is not defined (or delegate is set to nil) then it will take value of tableView.rowHeight
For performance reasons there was also added tableView(_:estimatedHeightForRowAt:) and tableView.estimatedRowHeight
The idea was not to calculate height of every cell during fast scrolling (such calculation may be costly) and use height that is good enough.
So that are the basics before constraints layout.
Then magic came. You can return UITableView.automaticDimension as height (by delegate method or by setting tableView.rowHeight). It will force tableView to calculate height from cells' constraints (note that constraints must define that height so very likely you want to set content hugging and resistance priority of every label, and you will encounter 'errors' in storyboard/xib).
Since that operation is costly you Apple forces you to specify estimated height by yourself. Also it's important to set that value to something that makes sense, otherwise things like programatically scroll won't work correctly.
This illustration shows what i'm trying to do:
The green list is the UITableView where it dynamically adjust it's height based on the number of items inside of it.
Underneath of the UITableView is a button that should follow the UITableView whenever it changes it's height size.
The UIButton should always be beneath the UITableView whatever the size of the UItableView.
I'm currently using autoresizing for UITableView
I have tried to use Autolayout but it seems i can't still find the answer.
i currently have no constraints in the layout.
This boils down to calculating the height of the table view that perfectly fits the cells. Basically you need to measure the size of every cell, then create a height constraint on the table view, and set its constant to the sum of the cells' heights.
Measuring the height of cells is tricky thought. If you only have a few cells (like in your illustrations), you can just instantiate all of them, keep them in an array and use systemLayoutSizeFittingSize to calculate their sizes. If you use multi-line labels, it is also important to set their preferredMaxLayoutWidth to appropriate values.
However, if you have only a few cells (and so cell reuse is not important), stack view is probably a better choice than table view. It's just too tricky to calculate the perfect height of a table view.
When use dynamic UITableViewCells height calculation via UITableViewAutomaticDimension you must set estimatedRowHeight. Is there any information about how does bad estimated value affect performance?
Estimated row height is not as important as you necessarily think. It is mainly used by the table to configure certain UI elements like it's scroll bars for instance. When tableView.reloadData() is called the table recalculates this anyway. As a general rule I just use an estimated row height of either 44 or whatever the height of my placeholder cell that is displayed whilst I am loading data is. The performance repercussions of using an inaccurate estimatedRowHeight are minimal to non-existent as it does not actually contribute to calculating the dynamic height of cells marked as UITableViewAutomaticDimention.
I'm currently trying to add infinite scrolling to a UITableView, which contains a number of calendar events. Since the event title doesn't always fit inside a single line I've added a multi-line UILabel to the cell. In order to calculate the height of the cell I'm taking advantage of UITableView's new self-sizing cells via Auto Layout in iOS 8. WWDC Session 226 talks about this in more detail.
In order to implement the infinite scrolling mechanism I'm overriding layoutSubviews where I need to calculate the height of a section which at the time given ist not visible on screen. This can be done by using [self rectForSection:0]. When doing so the table returns a height based on the estimated row size I had to define inside the table's initialiser in order to make self-sizing cells work.
self.estimatedRowHeight = 44.0;
self.rowHeight = UITableViewAutomaticDimension;
When the section gets on screen I get the correct size of the section, but since I have to update the table's contentOffset based on the computed height of that specific section this causes my table to jump up and down.
Any ideas on how to solve this? Is there a way to force a section to return the actual height and not the estimated one?
I have 3 labels in a UITableViewCell and have the labels set so they will wordwrap. If they word wrap the text goes into the next cell. How do I get AutoLayout to expand the cell based on the content without having to write code in the heightForRowAtIndex method? Isn't there a constraint I can use to automatically adjust the cell based on the contentView?
The cell looks fine if the text doesn't wrap in the label. Once it wraps that is when the problem occurs and I would like to have the cell resize to fit the content and have the same spacing between the bottom label and the bottom as there is between the top and top label.
Unfortunately no, you can't do this. A table view calculates its own total height first and has a fixed idea of the size of each cell as they load, it won't determine it's height from the outside in and it won't let layout constraints change the height of a cell.
If you think about how tables work, with cell reuse, then you couldn't really size the table from its cells without loading in every cell and adding it to the scrollview, and performing a layout pass on the whole thing. That would probably lead to quite poor performance.
You could experiment with populating a "free" cell (i.e a cell you've just instantiated, not added to a table) and laying it out for each row in your datasource when calculating heightForRow.
As you are loading the individual cells, after you fill the labels, but before you load the instance of the cell, check the label height.
Something like:
cell.frame.size.height
If the height is large enough that you know the label has wrapped to two lines, then increase the height of the cell you are about to load.