How to implement a self sizing UITableView? - ios

I want to dynamically resize my UITableView based on its content. As an UITableView has no intrinsic content size, I have to manipulate the height constraint programmatically. However, I want to have a max_height after which the UITableView stops growing.
With each reload, I set the UITableView's height constraint to its contentSize. If the contentSize is bigger than what fits into the defined boundaries, AutoLayout complains it "can not satisfy constraints simultaneously". So I tried giving the UITableView's height constraint the priority 999. In my understanding, if AutoLayout has to break constraints, it breaks the ones with the lowest priority first and tries to get as close to the constant as possible. (Perfect!)
If everything above the TableView is of fixed height, this works fine as seen in:
As soon as anything above the TableView relies on intrinsic contentSize (like a StackView with two labels) it doesn't work anymore. As soon as the height constraint gets too big, anything without a fixed height gets compressed. (Setting the compression resistance to 751 and content hugging priority to 249 does not change this)
To make it easier to verify I have created a GitHub project (https://github.com/Shanakor/SelfSizing-TableView/branches). On branch "master" you will find the working copy and on branch "not_working" you will find the alternative approach.
Thanks for your help.

Even after your change in Question description the reason is in constraints' priorities. Your stackview height is defined from its' intrinsic size. Which in turn is defined according to intrinsic sizes or height constraints of the arranged views (in this case it's intrinsic size of labels). So if you want your stack view to keep it's size (i.e. resist compression), the priority of the compression resistance(of the stack view and all its' arranged views) should be higher than the priority of table view height constraint.
So you need to set table view height constraint priority lower than UILayoutPriorityDefaultHigh (i.e. at least 749) and the default compression resistance for the stackView

Related

How to make a view height grow depending on its child views contents

I have a parent view whose height has to be decided by its child content.
How may I achieve this in storyboard without programatically changing it.
You can accomplish this using AutoLayout.
Make sure each of the child views has constraints defining its size and position. Then, set the parent view's vertical content hugging and compression resistance priorities to required. This will define the parent's height based on the height and positioning of its child views.
Note that depending on what the child views are, you may want to change their vertical content hugging and compression resistance priorities as well. For example, a UILabel with numberOfLines set to 0 can automatically grow based on its content, so you'd want it to hug its content vertically and resist vertical compression so that it resizes the parent view.
This image shows the parent (white) view with its vertical hugging and compression resistance priorities set in the inspector panel. Notice that the parent view has constraints set for its width, x-position, and y-position, but not its height. It's able to infer its height based on the height and position of the child views (see the next image).
This image shows the constraints of each child view. Notice that the vertical hugging and compression resistance priorities of these views were not changed. Each of these views has constraints for x-position and y-position, but you'll notice that not all of them have constraints for width and height. Views like the label and switch are able to automatically infer their size constraints based on their content. If you don't set vertical position constraints on every one of the child views, AutoLayout won't know how much space each of them needs, so it won't know how tall the parent view should be.
1- Add you View container and add constraints. Don't set height nor bottom spacing or set it but with less priority (example 999).
2- Add items/things to your View Container and add constraints. Be sure to add all require constraints plus add bottom spacing to the bottom item inside.
That will define the height of the container View.
PD: Forget about Content Hugging/Compression and Priority. They are handy but no use here. They just set a resistance to get bigger or smaller.

Multiple flexible height items - how to set the autolayout priorities?

Say you have (for example) a table cell layout with more than one dynamic flexible-height items,
They are linked vertically one to the other in the obvious way.
It seems very difficult to make this work, and very undocumented.
You'd expect that: you set the compression resistance of all the expandable items to 751. But that doesn't work.
After random experimentation, it seems to me that surprisingly you have to do something like this:
have the compression/hugging of the overall view on 250 and 750,
then strangely enough, for the three text views in the example, the priorities have to
...sequentially increase...
And, I think you have to make one of them "one lower" than the overall view - in the example one of them would be 749.
It's difficult/impossible to find the exact "formula" to make it work consistently.
What the heck is the logic of this? Is it just a pure bug in iOS?
Has anyone found the "correct formula" for making a number of expandables work in a cell?
priorities for the three text views
priorities for the overall holder view
cheers
Your question is not very clear to me. However, here's a good recipe to stack text views (whether or not you use a stack view for constraining them doesn't matter):
1. Disable scrolling for all your UITextViews:
Reason: If scrolling is enabled, a UITextView does not have a defined intrinsicContentSize. See this answer. Without an instrinsic content size, the content hugging and compression resistance ("CHCR") priorities have no meaning or effect.
2. Give each view along one axis a different priority for CHCR:
More precisely: Give all views that are connected with constraints along one axis a different CHCR priority that are not constrained in size along that axis (i.e. that don't have a fixed width / height constraint).
In your particular example, your setup shown in the screenshots is a correct solution:
Each of the three text views has a different content hugging and a different compression resistance priority. As a general rule you should start with the default values (750 / 250) and only slightly increase or decrease them as you did.
In case you use a table view with self-sizing cells (by setting its estimatedItemSize), the CHCR priorities won't matter at runtime because the cell will automatically resize to accommodate enough space for all three text views. You just need to set those priorities to "silence" Interface Builder.
If you use fixed-height table view cells however, the CHCR priorities are quite important because they determine
which of the views will shrink first if there's not enough space inside the cell (it's the view with the lowest compression resistance priority) and
which of the views will expand if there's more space inside the cell than the subviews actually need (it's the view with the lowest content hugging priority).
The CHCR priorities of the superview ("MV") is irrelevant as it's usually just a container view that does not have a defined intrinsicContentSize either. Its size is defined by the inner and outer constraints you add.
For more information on the CHCR priorities, see the chapter Intrinsic Content Size in the Auto Layout Guide.
When I am playing with cells that have a variable height I use:
tableView.estimatedRowHeight = 20.0
tableView.rowHieght = UITableViewAutomaticDimension
These two cause the table view render its cells with the appropriate height. In order for the height to be computed though you should set the cell's data in tableView(cellForRowAt)

Set constraints in autolayout so UIView wraps visible children

I am trying to achieve the following.
I have a
--> MainView
--> UIImageView 200x200
--> UILabel W:200(max) , H: Variable
--> UILabel W:Variable , H: 20
All the views in mainView are placed in sequence one after another.
Now i am trying to set autoLayout so that the mainView height is depended on its children,
E.g If i set ImageView hidden then it should wrap both UILabels etc.
How can I set autoLayout constraints so that the mainView have "Wrapping" effect over its children.
The easy way to achieve this is using a UIStackView (WWDC 2015 session video). Pure Autolayout is a lot more complicated in this case.
Assuming you would like to lay the children out vertically, left-aligned:
Controlling the Trailing Edge
Add a greater-than-or-equal 0 constraint between the trailing edge of each child and the parent's trailing edge. This will cause the widest child to push the parent's trailing edge to the right. These constraints should have a very high priority.
You will need another constraint to prevent the layout from being ambiguous. With the three trailing constraints the width of the parent is ensured not to be smaller than the widest subview. You also have to constrain the parent's width not to be greater than the widest subview's width. Just add a width constraint to the parent with a constant of 0 and a very low priority.
I like to think of that low-priority width constraint to work like a rubber band trying to pull the trailing edge as far to the left as it can. The greater-than-or-equal-to-zero constraint of the widest subview prevents it from pulling any further.
Hiding views does not have an effect since hidden views still take part in the layout calculation. You will need to keep a references to the greater-than-or-equal constraints and disable the corresponding constraint when hiding a child to take it's trailing edge out of the equation.
Controlling the Height
The heights of the children are likely defined by the view's intrinsic content size. Conceptually the Autolayout engine adds width and height constraints to the view according to the settings for content hugging and content compression resistance.
There will be two hidden height constraints for views that have an intrinsic height dimension: one for content hugging and one for content compression. Hugging constrains the height to be less than or equal to the intrinsic height. Compression resistance constrains the height to be greater than or equal to the intrinsic height. The height of the view is exactly equal to the intrinsic height if both can be fulfilled. The priorities for content compression resistance and for content hugging can be set separately for fine-grained control over when which constraint breaks.
We can use this knowledge to let the parentView's height shrink if a child is hidden. We need a "rubber band constraint" for the parent's height:
Constrain the height of the parent to zero with a low priority, say 2.
Whenever you hide a view, make sure to lower the vertical compression resistance priority of that view to a value less than the rubber band constraint priority, say 1. Now the rubber band overpowers the compression resistance constraint, causing the height of that view to collapse and the parent to shrink accordingly. Be sure to raise that priority to a value greater than the rubber band constraint when un-hiding the view to reverse the effect.
Now i am trying to set autolayouts so taht the mainView height is depended on its children
You cannot do this by constraints alone. Autolayout does not, in and of itself, normally size a view "from the inside out", i.e. by using its subview constraints. (The exception is for special self-sizing views like a scroll view's container view or a table view self-sizing cell.)
However, you can do it in code. This is what systemLayoutSizeFitting is for. You will have to perform manual layout on the superview, but you can do it easily by calling this method.
Each UI element requires 4 constraints to infer its bounds and position. The x position, y position, height and width.
Assuming you need to shrink the mainView to the height of the UILabels, set all the three constraints except the heightConstarintfor the mainView. ie, set constraints for x, y and width. Now set all the four constraints for the three child views. A constant value must be explicitly set for the heights of all the three subViews. Now the height for the mainView will be inferred from the heights of the child views. To wrap the labels, set the heightConstraint of the UIImageView to zero in code whenever required. An IBOutlet for the heightconstraint of UIImageView can be made to set it to zero.

can not set layout correctly when adding several uilabels to uitabelviewcell

I came across a problem when configuing the cell of UITableView. I add two labels vertically in the content view of the UITableViewCell, and I also add constraints for the top, leading and bottom layout attributes:
I think that the height of the cell can be caculated dynamically as I have set all the vertical layout and with the instrinct size of the label, the height can be inferred.
so, I can not understand the error message that IB told me.
The second problem is that the height of cell appear on the IB is not changed with the constraint I`ve make. If I decrease the bottom constraint for example, and it is the label to change its size to fit the constraint, but not the cell change its height.
If you need to add top , leading and trailing(or width) to the 1st label. Then add bottom ,leading and trailing(or width)for the bottom label. Then add bottom constraint for 1st label to 2nd label.Then by selecting both labels, add equal height constraint.It will solve your problem.
The meaning of this conflict is that when your label content is increasing dynamically, which label's content is needed to give more priority before whom.
More precisely it can be said that if you increase one of the label's content hugging priority i.e. 252 then that label's content increment and size will be given more priority for incrementing it foremost. As autolayout executes according to the priority of constraints, it faces ambiguity in terms of increasing the views of labels if you do not set the content hugging priority.

Label's height issue in tableView with auto layout

I use autolayout. I am displaying 2 labels in custom UITableViewCell. Label1 is above Label2. Their text is dynamic.
The issue is the height of one of the labels when displayed is larger than its text.
I tried changing their Content Hugging Priority.
So what happens is, if that priority is same or Label1's priority is higher, then Label1 is having exact height to fit its text but Label2 has larger hight than required. And when Label2's hugging priority is higher than issue is with Label1's height.
Any idea how to solve it?
It looks like you are expecting your cells to auto-size based on auto-layout's constraint information, but UITableView sizes it's cells using frames/autosizing masks. If this is the expectation and you aren't autosizing the cells like in this question, then your labels are going to either force-clip themselves in order to fit inside the cells with the sizes they were given from the table view or grow to satisfy all of the margin-constraints.
Since the content hugging and content compression resistance priority values are less than required (less than 1000), they are considered "optional" and will be satisfied as close as they can be without violating any of the other required constraints. This is why your label begins growing (or clipping itself).
This can be solved in a couple of ways off the top of my head:
If you don't care about the cell having a variable height and are fine with the labels migrating toward the top of the cell, then make the constraint that pins the bottom label to the lower edge of the superview be non-required. More specifically, make that constraint have a priority lower than the vertical contentHuggingPriority for both of the labels. This way the content hugging priority constraints will take precedence over the lower constraint.
Make your cells auto-size themselves (using auto layout or otherwise) so that the system never has to consider the vertical contentHuggingPriority of each label 'optional'.
I solved this after experimenting with lot of things. The only thing I had to do is to set horizontal and vertical content compression resistance priority to required.i.e. 1000.
I did this for all labels because I don't want any of the labels to trim their content.
One more thing which is too much important is Getting Right Height Of Cell. If there is even 1pt of error in calculating custom cell's height it will not be displayed as expected.
Hint :
If height of any view is greater than expected then possibly calculated height of cell is greater than what is actually required.
If any of views is shrinking vertically or not displaying whole content then possibly calculated height of cell is lesser than what is actually required.
Yoy can test if height is wrong by adding/removing constant value to height (variable) you calculate for cell.

Resources