How to use auto layout so that the following layout is achieved? - ios

I have a vertical UIStackView whose top, leading and trailing edges are pinned to the superview and whose height is determined by its subviews.
Its arranged subviews consist of 5 horizontal UIStackViews each of which have 3 UILabels as their arranged subviews.
For each of the horizontal UIStackView, the text of the first UILabel is the heading, the text of the second UILabel is the colon and the text of the third UILabel is the info as shown in the following image
All of the UILabels have numberOfLines = 0 so that they are multiline.
I would like to create constraints using auto layout so that the following conditions are met-
The colon UILabels are all vertically aligned
The width of the info UILabels are at least 20% of parent view
The width of the info UILabels are at most 60% of parent view
If the width of all the info UILabels is less than 60% of parent view ( see 3 ), then it must shrink to the width of the widest info UILabel.
I understand how to use auto layout to create constraints to satisfy the first 3 conditions.
However, I do not understand how to create constraint to satisfy the 4th condition. I tried increasing the "Content Hugging Priority" but it shrinks the label to 20% of the parent view ( see 2 ).
Can anyone point out how to achieve this layout?

This is considerably more complex than it would appear on the surface.
The "Info" column has to be evaluated for min/max width and word-wrapping, and the "Heading" column has to be evaluated for word-wrapping based on the "Info" columns variable width... and the "Info" labels have to match widths based on the widest wrapped (or not) text.
That's a lot for auto-layout to handle.
I don't think it can be done with constraints only. The main problem being that auto-layout calculates the height of the height of the Heading labels based on the potential for the Info labels to be at their widest.
So, we can end up with this:
and the Heading label text is no longer vertically aligned to the top of the label.
The best I could come up with is to override viewDidLayoutSubviews(), using a counter which forces auto-layout to calculate the Info labels first and then the Heading labels.
As to constraint settings...
The Heading labels need nothing special - just set the number of lines to Zero to allow word-wrapping.
The "Colon" labels don't need constraints, but they do need both Horizontal Content Hugging and Compression Resistance set to 1000 (required), because we don't want them stretching or getting squeezed.
The Info labels need:
Horizontal Content Compression Resistance: 1000
Horizontal Content Hugging: 999
Width: >= its stackView Width with Multiplier 0.2
Width: <= its stackView Width with Multiplier 0.6
Width: = its stackView Width with Multiplier 0.2 and Priority: 999
and finally, the bottom 4 Info labels need Width constraint equal to the top Info label.
The horizontal stack views that make up the 5 "rows" should be set to:
Alignment: Top
Distribution: Fill
Spacing: 8 (horizontal spacing between the "columns")
The vertical stack view that holds the 5 "rows" should be set to:
Alignment: Fill
Distribution: Fill
Spacing: 10 (vertical spacing between the "rows")
I've put up a project on GitHub that demonstrates the layout -- it has 10 different "variations" of label content to show how the sizing works: https://github.com/DonMag/MutlilineVarWidthLabelColumns

you can achieve this kind of UI by using min and max constraints.
you can follow the below constraints for it.
give max width to Heading label with the relation of the screen.
give fix width to colon label
Give leading, trailing, top, bottom to info label
change Horizontal content hugging priority of info Label to 252.
please check the below images for more details.
The actual structure of my example
Constraints for Heading label
Constraints for info-label
output
If you need any help then feel free to ask again.

Related

How to horizontally align two UILabel (with fixed and variable width)

I have been facing issues horizontally aligning two UILabel and one UIImageView like this:
First label has variable width, can be truncated if long. Second label has fixed width, it should always be aligned to right of UIImageView. It should never go off screen. UIImageView is aligned to right of first label.
I have tried embedding them in horizontal UIStackView but the image + second label always aligns to end of cell. Got the same issue when trying without UIStackView.
Please help.
You can embed both label and horizontal StackView into another horizontal stack view. Then, you'd need to set the dynamic width Label's Content Compression Resistance Priority (you can find this property at the bottom of the Size Inspector), to be smaller in order for it to shrink.
Then on the container StackView (the one that contains all views), you'd need to set constrains to top, bottom, leading to 0 to the superview and the trailing to be greater than or equal to 0, for it to not take all space of the superview, but at the same time not get offset if the content is too wide.
I hope that is clear enough!

Dynamic height for a horizontal UIStackview

I have a horizontal StackView with distribution as "fill equally". It has 3 labels.
I want one of the labels to have dynamic height.
When I set the number of number of lines to 0 for that label, it ends up breaking the constraint the spacing constraint at runtime.
All these horizontal stackviews make a one vertical stackview.
I have tried setting lower vertical hugging priority and higher resistance priority to the multiline label
Also it behaves like this in the XIB
Before changing the number of lines
After changing the number of lines
You do not need to set Content Hugging and Compression resistance property to achieve that effect.
Horizontal Stack Views: Set Alignment "Fill" & Distribution "Fill Equally"
Vertical Stack View: Set Alignment "Fill" & Distribution "Fill"
Set Label Lines to 0.
Set Top, Bottom, Leading Constraint for Vertical Stack View.
Set all constraints for UIStackView.
Update the UIStackview height constraints constant value based on UILabel text height
Not Directly answering the question rather on how to control the dynamic height of UIStackView.
Vertical Stack View. Make it free of height like no bottom constraints or no height constraints.
Contents(like view) are fixed height.
Both Alignment & Distribution of UIStackView will "fill"
now show or hide the contents then Stactview height will dynamically change.

Positioning content relative to something else

I have three labels in my view:
The first label is a description and as the text will be fetched from an API it varies in length.
First question is how do I make the description label auto-adjust its height?
Second question is how do I make label 1 move relative to the height of description label and also label 2 relative to label 1.
You change the number of lines to 0 and line break mode to word wrap to make a label arbitrarily tall. You embed all three labels in a vertical stackview and set the spacing in the stackview to distribute them with equal spacing. Constrain the stackview to the top, leading and trailing edges of your view. You do not need to give it a height as it will derive its intrinsic height from the labels plus the spacing.
you should definitely take a look at autolayout:
https://developer.apple.com/library/content/documentation/UserExperience/Conceptual/AutolayoutPG/
How to auto adjust height
descriptionLabel.sizeToFit()
How to auto adjust height in relation to another label
label.frame = CGFrame(x: decriptionLabel.frame.origin.x, y: descriptionLabel.frame.origin.y + descriptionLabel.frame.size.height, width: descriptionLabel.width, height:0)
label.sizeToFit()
note: You might want to add some padding to the y co-ordinate otherwise it will literally start where the description label ends.
You can do all what you want to do without writing a single line of code.
To answer your first question : For the description label set number of lines to 0;
To answer your second question You can do it with 3 simple steps using auto layout :
A) Description label: set constraints to : TOP to superview , leading space to superview , Trailing space to superview, Bottom space to label 1.
B) Label 1 : leading space to superview , Trailing space to superview, Bottom space to label 2.
C) Label 2 : leading space to superview , Trailing space to superview.
Auto layout will adjust and move desire elements based on contents.

When can auto layout infer width and horizontal position?

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.

Dynamic table view height

The idea is to resize height of the cell automatically based on few controls heights. As you can see on image above I have Top Label and Bottom Label. These two labels can have different height based on text length.
Few words about setup in storyboard.
I set number of lines to 0 for 2 described labels to allow grow their height dynamically based on given text.
For the Top Label I have next constraints:
For the Bottom Label I have next constraints:
So I we say about vertical spacing between 1000 green label and bottom label it's every time the same:
But without this spacing cell won't stretch height. How can I reduce this vertical spacing? Because there is to much spacing between "1000 green label" and bottom label in case if top label have big height because of text.
In -viewDidLoad method I set:
[self.theTableView setEstimatedRowHeight:145];
[self.theTableView setRowHeight:UITableViewAutomaticDimension];
Seems it works pretty cool sometimes with a bug described here but I don't know how to restrict that vertical spacing:
http://www.appcoda.com/self-sizing-cells/
Set the bottom label's top constraint to be >= 11.5 (or whatever its minimum spacing should be).
This will let the cell adjust that vertical spacing, depending on the other content in the cell.
Update:
In iOS 9, this would much more simply be handled by UIStackView.
A horizontal stack view would constrain (and determine the cell height based on the) two inner vertical stack views. The left vertical stack would handle the image, banner, and label layout, and the right vertical stack would handle the top label, 10000, and bottom label layout. You'd only need with 4 constraints (for the horizontal stackView to constrain it to the contentView).

Resources