Nested UITableViews with autolayout - ios

I've got a layout with nested UITableViews (each UITableViewCell's contentView has as unique child another UITableView).
All the leaf cells are correctly set up with autolayout (in fact, when they are presented in a single table they are displayed ok).
But when they are inside the inner table, the outer table does not calculate the correct heights for cells, leading to their standard height of 44. I'm using
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 260;
for both inner and outer tables.
How can I get the tables to show correct heights with autolayout? (I don't want to override tableView:cellForRowAtIndexPath:, I want autolayout to do the trick).
Thank you in advance

I personally think that nesting tableview is a bad UX experience. Better you'll find a different approach for your users.
Saying that they also lead to different problems, for instance who scroll first etc.
In your case the issue is due to the fact that UITableViewAutomaticDimension simply send to the cell content view -systemLayoutSizeFittingSize(or similar) with UILayoutFittingCompressedSize, that for a scroll view means 0;0 basically your table view is not counted while calculating the cell height.
In my opinion now you have 2 options:
make your calculus manually and cache the result for a faster
scrolling
create a subclass of a UITableView that when asked for its intrinsicContentSize returns a custom size
Really hope this helps.

Related

What actually defines the height of a UITableViewCell?

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.

Is it possible to use self sizing cells within self sizing cells?

I want to have self sizing cells within a tableview with self sizing cells. But on first initialisation some cells are not correct. After a scroll to the bottom and the cells will display again the cell size is correct. In the screenshot you can see that the top 2 cells have a white space at the bottom and the third one doesn't. They should all look like the third one.
This is the issue in my main project.
Screenshot
The problem with automatically sizing cells inside automatically sized cells is a bit tricky because you have to understand how it works. UITableView works with estimates most of the time. It usually does not calculate the contentSize precisely because to calculate it precisely, it has to first instantiate every cell, layout it and then calculate its size.
The precise values are calculated only for cells that are displayed (visible in current scroll frame).
The tricky part is that the inner cells (inside your outer cell) are not displayed until the outer cell is displayed therefore the outer cell does not have size calculated correctly. Also note that UITableView does not automatically update cell heights unless explicitly said to do so.
The solution, if you really have to do this, is to calculate the height of the outer cell correctly before it is displayed and manually set a height constraint.
If you know the height (from data source), it's easy. If you actually need to calculate the height of the inner table, you can do something like this:
// make the table high enough to display all cells
innerTableHeightConstraint.constant = 2000
// reload table
innerTable.reloadData()
// force layout
innerTable.layoutIfNeeded()
// now the contentSize is correctly calculated
innerTableHeightConstraint.constant = innerTable.contentSize.height
The whole concept is tricky and ideally you should prefer using UICollectionView or table sections. When you are using inner table views, there won't be any cell reuse for the inner tables and your performance will suffer.
You should not wrap UITableView into UITableViewCell, try to use UITableView Sections instead to add an extra depth level to your UITableView.

Attach/Stick a UIButton under the UITableView

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.

Dynamic height of two UITableViews embedded in a single view with Auto Layout

I`m trying to adjust the height of two tableviews embedded in a single viewController. .
This is how it is currently displayed
Current constraints
If possible, I want to adjust the height of each table to avoid the scrolling effect of the table (it is not possible when the total content of the tables is higher than the screen, in that case the height of each table must be the same )
I have manually changed the constraints to show you what behavior I want
Expected behavior 1
Expected behavior 2
I have no idea what constraints I have to modify and what part has to be programmatically
Here is a scenario:
Mapping Data to table.
Get tableview's content size
Set the constraint = height of the content size
Update constraint layout.
In your case, i would like to recommend to use 1 table view with 2 sections.
Hope this help.
The best solution would be not using tableView for the first section as its not a complicated content to be displayed on tableView. Even if you want to use tableView, try this.
Take height constraint of the top tableView. Take outlet of the height constraint in your controller. Then just after you are reloading the tableView, assign the contentSize.height to the constraint and call self.view.layoutIfNeeded(). This will change the height of the tableView to be same as content.
You can do same with bottom tableView.

iOS Calculate Height For Cell

I have a problem with Apple's dynamic resizing cells such that the logic for the layout of my cells isn't as cut and dry as just a few stacked growing UILabels.
As a result, I can't really use the dynamic resizing option they've provided and so I need to manually calculate the height of my cell using NSString boundingRect methods.
That's fine - it works, but I end up needing to store a lot of constants that keep track of my auto layout constraints. I feel like this is counter intuitive to what auto layout is supposed to do for me, so I'm not sure if this is the correct way to implement heightForRowAtIndexPath.
I essentially have to go and copy my constraints into a constant and then use those values in a class method to generate my heights. Apple provides very little internal insight into how UITableViewAutomaticDimension works, but it's clear that the height of the cell is still calculated BEFORE it is laid out. Thus I can't really add any complex logic to it unless I know what methods are called before.
Any ideas on what I should do, or if my approach is ok?
The common solution I can offer is to add a height constraint fro your cell and change the constraint according to your needs, whatever they are. UITableViewAutomaticDimension will resize cell to the height you specify with this constraint automatically.
If your table cell is custom then its easier if you use a method like configureCell and pass the indexPath to it from cellForRowAtIndexPath and then determine the layout based on the data that you have and see where you want the left and right label to be placed. Once you have done this store the height required in the model or perhaps another array that has the same number of rows as your table and use it to return in heightForRowAtIndexPath.
This is easier and gives you flexibility without having to fiddle with too many delegate methods of table view. Centralise your layout logic in one place.
Another alternative is to override layoutSubviews of the table view cell and calculate the height there and store it.
If you want your tableview cell to be assigned a default height, you can use estimatedHeightForRowAtIndexPath: available in UITableViewDelegate. As per Apple guidelines:
// Use the estimatedHeight methods to quickly calcuate guessed values which will allow for fast load times of the table.
// If these methods are implemented, the above -tableView:heightForXXX calls will be deferred until views are ready to be displayed, so more expensive logic can be placed there.

Resources