When can auto layout infer width and horizontal position? - ios

I'm trying to lay out the following simple UI using auto layout:
So far, I've set 5 constraints:
The left edge of the textfield is constrained to the left alignment guide
The top edge of the textfield is contained to the top alignment guide
The right edge of the label is constrained to the right alignment guide
The top edge of the label is constrained to the top alignment guide
The right edge of the textfield is constrained to be 20 points from the leading edge of the label
Based on these constraints, I would think that the width of the textbook would be determined by the size of the label, and horizontal layout for both would be implied. This is indeed how these constraints behave when I change the size of the button, but I'm still receiving the following warning in xcode:
My question is why? Width and horizontal position can be determined by the information provided, and providing additional information (like a fixed width) would only mess with the flexibility of the interface.

Well Width can't be calculated based on the constraints you applied.
Both textField and label grow and shrink based on content.
What will happen If at runtime I add much more text or increase text(content) of both the fields. Then there will be ambiguity about which field to fill full and which to trim.
So one thing need to be fixed based on that other width can be calculated.
You can add one Extra width constraint to Add Activity.
My extra width constraint would be less than or equal to the width you assign. And its priority would be greater than its leading.
Or you would want to play with content hugging priority. With changing content hugging priority one can make it possible for label not to grow.
Cocoa Autolayout: content hugging vs content compression resistance priority
If you don't want to add Extra width constraint Then increasing horizontal hugging of Add Activity to 251 (greater then textfield hugging) will resolve the ambiguous constraint issue.

Related

what scenarios of layout design needs Dynamic height label and a text field next to each other?

Not sure where one needs to use Dynamic height label and a text field next to each other and
how the constraint recipe works using a required, greater-than-or-equal constraint and an optional constraint to set the vertical spacing of the controls based on the tallest control at runtime?
Taken from Apple Docs -
"This recipe dynamically sets the vertical spacing of the controls
based on the tallest control at runtime. If you increase the label’s
font size to 36.0 points, then the layout’s vertical spacing is
calculated from the top of the label instead. If you increase the font
size of a label, you would typically also increase the font size of
the text field. However, given the extra large fonts available through
the iPhone’s accessibility settings, this technique can prove useful
when mixing dynamic type and fixed-sized controls (like images)."
Reference: https://developer.apple.com/library/archive/documentation/UserExperience/Conceptual/AutolayoutPG/ViewswithIntrinsicContentSize.html#//apple_ref/doc/uid/TP40010853-CH13-SW8
Solution screenshot:Views with/without constraint recipe for Dynamic height label and a text field
Even though your question is a little unclear, let's see if this helps explain things...
Suppose we start with 2 labels - LabelA (green) and LabelB (cyan):
LabelB is constrained 20-pts below LabelA... as we add text to LabelA, it will grow in height, "pushing" LabelB down and keeping the 20-pts vertical spacing:
All of that is pretty simple, and it's exactly what we expect to happen.
But, suppose we want LabelB to start at 100-pts from the top of the view (the safe-area), and only move down if LabelA gets tall enough?
If we add a Top constraint to LabelB, that will stretch LabelA:
Or, it will compress LabelA:
In both cases because LabelB Top cannot be both 100-pts from the view Top AND 20-pts from LabelA Bottom at the same time.
So, let's change LabelB Top to at least 20-pts from LabelA bottom, by setting it to >=, and, we'll change the LabelB Top constraint to a priority of less-than-required. In this example, we'll use Default High (750).
What we've done is told auto-layout to break the 100-pt Top constraint if needed. So, if LabelA is short, LabelB can be at 100-pts, but if LabelA gets tall, keep LabelB 20-pts below LabelA and break the 100-pt Top constraint:
So, it's not so much the order in which the constraints are evaluated, as it is the logical ability for auto-layout to satisfy all the constraints.
Edit
To try and explain the specific example from Apple...
Let's start with a minimum set of constraints, where the fonts are:
Label - 30pt
Field - 14pt
The Name Label is 40-pts from the Top, and the Name Text Field is constrained to the FirstBaseline of the label.
Now let's change the fonts to this:
Label - 20pt
Field - 30pt
so the Field is taller than the Label, here's what we get:
The Top of the label is still 40-pts from the Top, but the field has been moved up (to maintain the baseline alignment) and the Top of the field is now only (about) 25-pts from the Top.
The goal is to keep the Top of tallest element at 40-pts. With only these two vertical constraints, as we can see, we failed.
So, let's reset the fonts to:
Label - 30pt
Field - 14pt
and add the additional constraints with specified Priorities:
The positioning is identical to the first example, but... if we again change the fonts to:
Label - 20pt
Field - 30pt
this is the result:
The Top of the field is at 40-pts, and the Top of the label has moved down (to maintain the baseline alignment).
And... we've accomplished our goal!
Edit 2
To try to clarify with "plain language"...
The Baseline constraint will control the relative vertical alignment of the two elements. As we change the font size of one, it will make that element "taller" (or "shorter") and the other element will move vertically to keep the baselines aligned.
We've added a required constraint of top >= 40 to each element. That tells auto-layout "don't let either element be closer than 40-pts from the top."
We've added a constraint of top = 40 to each element, with Priority: 249. That tells auto-layout "try to put the top of each element at 40-pts... if you can't (because the baseline alignment is pulling it down), then go ahead and break that top constraint."

UIStackView with "wrap content" width

Problem:
Create horizontal UIStackView with 2 elements
Item width with "Short text 2" should be "wrap content". Item with "Long text 1" should fill all free space.
Please help to understand, how it can be created in code (Swift language)?
Step 1: Drag a StackView add left, right, top/bottom constraint to it. (ignore error for now)
Step 2: Drag two labels and add it to stackView (continue to ignore warning)
Step 3: Now you need to resolve horizontal content ambiguity so change the content hugging priority of left label to 250 while leaving the right labels content hugging priority at 251
Step 4: Now comes your question of warp content for vertical height :) Simply select the right label and set its content compression resistance to 999 while leaving left label's content compression resistance at 751
If you are wondering whats happening with content compression resistance and content hugging priority and all, please read apple docs on the same for more detail
Just to give a gist of what am doing, in iOS UILabels have implicit content size, so they take size based on content. So is Stack View. Now because StackView has to deduce its height based on two labels it has and both of them have same content compression resistance and end up having different height in order to resolve confusion it looks for further assistance from you. You do that specifying that right label resists more to change in its implicit vertical height compared to left label.
That means now stack view knows that it has to consider right labels height to adjust its height while it can override the implicit size of left label :)
Confirmation of logic:
On changing right label font stack view height automatically increased and increased the height of left label as well :)
EDIT on Comment by OP:
As OP suggested in comment that conern is width and not height, step 4 is not necessary, your problem is resolved at the end of step 3. On setting content hugging priority it self
on changing right labels content UI updates as shown below
Thats all :) Hope that helps

iOS autolayout disregarding "trailing space" constraint, using width/content size instead?

I have a UILabel (the subtitle) that I want to have a static X origin, but extend to the edge of its nearest neighbor. There's a button ("Visit Link") that is optionally removed from the superview at runtime if not needed. The constraint from the label to the button has a priority of 1000, and the constraint from the label to the superview container has a priority of 250:
However, when I run the application removing the button (via .removeFromSuperview() in the viewDidLoad method), via the view debugging I see that the content size is setting the width of the label, taking priority over the constraint I have set.
I expect the label to extend to the edge of the view, but as you can see, the constraint is greyed out - I assume trumped by the (content size) constraint instead:
Does the (content size) constraint have a higher priority than my Trailing Space to: Superview constraint? And how can I change it, since it's not a constraint I've even defined?
When you remove the button from the view hierarchy, that also removes any constraints involving the button. So, all that's left is the trailing constraint to the superview at priority 250.
The label has an intrinsic width based on its content. That means that its horizontal content hugging and compression resistance priorities come into play. Its content hugging priority is 251.
That means that it's more important to the auto layout system that the view's width be no larger than necessary than it is to keep its trailing edge at 8 points from the superview's trailing edge.
You should probably increase the priority of the trailing constraint. You want it to be less than the trailing constraint to the button so that, in the case where the button is present, it doesn't conflict. You also want it less than the button's compression resistance priority, so that the button doesn't get squished to allow the label to be 8 points from the superview. But, other than that, you want it to be as high as possible. (In the hypothetical case where you simply got rid of the possibility of there being a button, you would normally make that trailing constraint required, right? So, it should be as close as possible to required without causing undesirable side effects.)
If you're targeting deployment to iOS 9.0 or later, you should consider using a UIStackView for this layout. It will take care of some things for you, like adding or removing the appropriate constraints when the button is hidden or shown.
The (content size) constraint, automatically installed by the system at runtime, seems to have a priority somewhere between 250 and 750. When I use 250 or 251 for my Trailing Space constraint, it does not work.
However, bumping the priority of my Trailing Space constraint up to the Xcode-titled High priority of 750, allows it to take precedence. So the defaults for the width of UILabel seem to fall "somewhere in the middle."
Autolayout, you silly.
From the second screenshot, it looks like everything is working as it is supposed to.
Two things I'd like to mention:
Always try to set up your layout so you have as few constraints as possible. Each constraint adds complexity.
Yes, content size of the label does indeed have a higher priority. That's why the label is not resized to the right-most edge, which is good. What you are seeing is the label's intrinsic size which means that UIKit knows how big the label is supposed to be drawn, depending on font, text, etc.
To make this layout a bit more robust, I'd change the Trailing space of the label so that it has priority 1000 but is >= 0.
This way, the layout will be valid with or without the Visit Link button and the content size of the label (it's intrinsic size) will resize it to whatever length it needs to be, but no more than the right edge of its superview.
Hope this helps!
UPDATE: Wrote a quick post on why this content size is appearing and why you should use it to your advantage.

Equal width and equal spacing between buttons in autolayout

I'm trying to use autolayout constraints to automatically resize a few similarly-sized buttons in a view to give the following effect:
Before resizing
Desired effect after resizing
As you can tell, I want the buttons to be of the same size and I also want the spacing between each button to be a constant 20 points. It seems pretty simple at first, so I set the following constraints:
Buttons: space from left neighbour = 20 (inclusive of left-most and right-most buttons)
Buttons: space from right neighbour = 20 (inclusive of left-most and right-most buttons)
Buttons: same width
What actually happens after resizing
When in preview or when I test run the app in my iPhone/simulator, the button resizes and doesn't even follow the same width constraint I set for it. In fact, the view containing the views also resizes to fit the new button sizes. Anyone knows how to fix this problem purely in the interface builder?
Setting:
- equal widths of all buttons
- horizontal spacing between all buttons
- leading to superview for the first button and trailing to superview for the last button
should do the job. Unless you're having problems with the superview (e.g. ScrollView missing constraints)
In the interface builder you set the spacing constraints between buttons like you described above. Then you can command-select all of them and specify the "Equal Width" constraint to apply to the selected objects.
Finally I have oblivion how to solve this problem. I've test it works like charm.
add constraints to space items with 20 units margin
add constraint to ages
now tricky part
for each item add constraint equal widths to a parent
select all this new constraints and change its properties
set multiplier to value 1:5
set constant to -24 (6 separation between items and parent edge gives 120, this multiplied by multiplier value 1:5 gives 24)
update items frames
That's it! Picture below show how it works in interface builder:
Set simulated size to "freeform" and test different widths (I sett this to 330).
This problem is seems to be because of wrong content hugging priority and content compression Resistance priority. So you should set them as low content hugging and high compression resistance (all should have same value).
Because content hugging is the property that resist a view to grow and content compression Resistance priority is to resist a view to shrink. For more information regarding these you can found this Question.

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