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.
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.
I want to use UITableView and UICollectionView with autolayout. However since they don't have the intrinsicContentSize method, it is hard to set constraint for all the views.
Is it OK if I modify the intrinsicContentSize method of them to return the contentSize for UITableView and self.collectionViewLayout.collectionViewContentSize for UICollectionView?
Regarding intrinsicContentSize the documentation states that:
Overriding this method allows a custom view to communicate to the
layout system what size it would like to be based on its content.
As UITableView and UICollectionView are both subclasses of the UIView, this method must work for them too. I don't see any cautions in the docs about using it with them.
You can also check out this great tutorial concerning Auto Layout, and Intrinsic Content Size in particular. Pay attention to the following:
To implement an intrinsic content size in a custom view, you have to
do two things: override intrinsicContentSize to return the appropriate
size for the content, and call invalidateIntrinsicContentSize whenever
something changes which affects the intrinsic content size.
So don't forget to call invalidateIntrinsicContentSize when your table view reloads data, inserts new rows etc. And the same applies to the UICollectionView.
Hope this helps.
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.
I have a static UITableView with a UITableViewCell that contains a label. I have it set up to look nice right now, but when a user has their Dynamic Type turned to a larger setting, the label is cut off.
The UILabel has static ~15px constraints set up on all four edges of the UITableViewCell's content view.
How can I make the UITableViewCell's height change dynamically as the Dynamic Type adjusts the size of the UILabel's body formatted text.
Subscribe to UIContentSizeCategoryDidChangeNotification and when you get notified change your heightforRowAtIndexPath accordingly.
I know you asked this question many years ago but, as you didn't share any solution here, I guess you didn't find one.
Standard table view cells content is automatically adjusted thanks to the cell-sizing feature.
Constraints must be adapted in case of custom cells in order to obtain the desired rendering and let the cell-sizing work.
If your table view cells don't size to fit content, try and use the properties UITableViewAutomaticDimension associated to estimatedRowHeight.
Finally, check out this WWDC video detailed summary dealing with the best way to build apps with Dynamic Type if the explanations above aren't enough.
When using autolayout, calling intrinsicContentSize seems to be the method to determine what CGSize is required to properly fit the views content.
However, this method is only supported for a limited number of existing UIViews.
Anytime that I make a custom view, even if it is something as simple as a UILabel inside of a container UIView, that containing view is unable to determine its intrinsicContentSize (returns -1).
I don't understand why the view is able to properly be displayed on the screen yet the view doesn't even know its own height...
The UILabel in a container view is a simple example but I'm dealing with slightly more complicated UIViews where there are maybe 15 views nested within eachother. In order to determine the size of the view which contains all of its subviews, I have to manually create my own intrinsicContentSize method and do very time consuming work where I have to sum up all the heights of the subviews plus add to that all of the constraints.
This process is terrible. It's very easy to miss out on a height somewhere by forgetting to add the height of one of the subviews or constraints. Also, the matter is further complicated by the fact that with dynamic subviews. For example, if the view has 2 columns of dynamic subviews, you need to manually find the height of the subviews+constraints for each column, compare these heights and return the larger of the two. Again, this is a simple example but often it's not so simple, causing many many migraines.
Back to what I was asking earlier. How can iOS display the view yet not even know how tall the view is? There must be some way to find out what that height is. Thanks for reading.
Here is an image to help visualize what I want.
Are all your subviews using auto-layout themselves? I mean that if your using auto-layout to place MyCompositeObject, is that composite object using constraints internally to place its many objects? I've found that if so, then the intrinsicContentSize will account for all the subviews, but if not, your UIView's intrinsic content size is going to end out returning something inaccurate and small.