ios autolayout dynamic UILabel height - ios

Say I have three UILabels whose positions are like below:
[Label1] [Label2]
[Label3]
Label1 and Label2 are in the same row and Label3 is below them. All the labels will have a fixed width and will contain dynamic text, so their height will vary.
How do I make the Label3 10 points below the label which has a higher height using AutoLayout?
For example, if Label1's height is 100 points, Label2's height is 120 points (their Y positions are the same), then Label3 should be 10 points below Label2, but if Label1 is 120 points high and Label2 is 100 points high, then Label3 should be 10 points below Label1.

You simply make constraints between both Label3->Label1 and Label3->Label2. Use inequality constraints. There will be only one way to satisfy both!
You will also need a top constraint for Label3; its constant should be very small and its priority should be very low. This will give the two inequality constraints something to "aim at".
Here is an example. This as achieved entirely without code - the buttons have code to add text to the labels, of course, but the constraints are configured entirely in Interface Builder; the labels are resizing, and the bottom label is moving down, automatically. (You can construct the same layout in code if you want to, naturally.)

I suggest you to wrap top two labels to UIView and setup constraints so these labels fit all space inside that view. Then you simple add vertical spacing constraint to bottom label3 with constant = 10. In that case top view will have size of larger label and will satisfy your conditions

I thought this would be an interesting exercise so I create a little test project. The gist of the code is below. You can just copy/paste it in the standard Single View iOS template.
(Note that I use SnapKit for programmatic Auto Layout because it is so much simpler than the UIKit API. I find it even much simpler than doing things in Xcode.)
The result is exactly the same as Matt's great screencast.
// ViewController.swift
import UIKit
import SnapKit
class ViewController: UIViewController
{
override func viewDidLoad() {
super.viewDidLoad()
let leftLabel = UILabel()
leftLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "addText:"))
leftLabel.userInteractionEnabled = true
view.addSubview(leftLabel)
leftLabel.numberOfLines = 0
leftLabel.text = "All the world's a stage, and all the men and women merely players: they have their exits and their entrances; and one man in his time plays many parts, his acts being seven ages."
leftLabel.snp_makeConstraints { (make) -> Void in
make.top.equalTo(40)
make.left.equalTo(self.view)
make.right.equalTo(self.view.snp_centerX)
}
let rightLabel = UILabel()
rightLabel.addGestureRecognizer(UITapGestureRecognizer(target: self, action: "addText:"))
rightLabel.userInteractionEnabled = true
view.addSubview(rightLabel)
rightLabel.numberOfLines = 0
rightLabel.text = "There is a tide in the affairs of men, Which taken at the flood, leads on to fortune. Omitted, all the voyage of their life is bound in shallows and in miseries. On such a full sea are we now afloat. And we must take the current when it serves, or lose our ventures."
rightLabel.snp_makeConstraints { (make) -> Void in
make.top.equalTo(40)
make.right.equalTo(self.view)
make.left.equalTo(self.view.snp_centerX)
}
let bottomView = UIView()
view.addSubview(bottomView)
bottomView.backgroundColor = UIColor.redColor()
bottomView.snp_makeConstraints { (make) -> Void in
make.height.equalTo(20)
make.left.right.equalTo(self.view)
make.top.greaterThanOrEqualTo(leftLabel.snp_bottom)
make.top.greaterThanOrEqualTo(rightLabel.snp_bottom)
}
}
#objc func addText(recognizer: UIGestureRecognizer) {
if let label = recognizer.view as? UILabel {
label.text = label.text! + " I like cheese."
}
}
}
Updated the code to add some additional text to the labels when tapped.

First of all remove height constraints and set all 3 labels vertical Content Compression Resistance Priority to 1000. This is the most important part.
Then add vertical space from Label3 to Label 1, and set instead of Equal, Greater Than or Equal with priority say 500. Add same space constraint to Label2.
Last add constraint from Label3 to Top = 0, but set priority to 1.

Related

Horizontal UIStackView : Does value 999 or 1000 carry any special meaning in Horizontal Content Compression Resistance?

I have the following 3 components within a Horizontal stack view.
Yellow UILabel
Red UIImageView
Green UIImageView
The distribution for the Horizontal stack view is Fill.
The content mode for UIImageView is Aspect fit. They are using SFSymbol named "pin.fill".
The number of line for UILabel is 0, so that it supports multilines.
All the 3 components are having same Content Hugging Priority (250) and Content Compression Resistance Priority (750)
When Horizontal Content Compression Resistance for UILabel is 750 to 998
My questions are
Why the red UIImage take up most space, even though all 3 of them are having same Content Hugging Priority (250) and Content Compression Resistance Priority (750)?
My intention is letting yellow UILabel fill up most space. However, I can only achieve so, if I increase the Horizontal Content Compression Resistance (For UILabel only) up to 999 or 1000. I thought, as long as any value is higher than 750 will be good enough? Does 999 or 1000 value in Horizontal Content Compression Resistance carry any special meaning?
When Horizontal Content Compression Resistance for UILabel is 999 or 1000
Please do take note that, even when Horizontal Content Compression Resistance for UILabel is 999 or 1000, both red & green UIImageView's width will be compressed. But, they are compressed with different strength, which end up 2 UIImageView are having slightly different width. Why?
p/s
The complete project code is located at https://github.com/yccheok/stackoverflow/tree/master/66444344/test
You're over-thinking things a bit.
What you want to do is tell the two image views to "hug" their content, and leave everything else at the default Hugging and Compression Resistance values.
So, Label properties:
Both "pin" image views:
It will look like this in your Storyboard:
Give this code a try... connect the label to the #IBOutlet and run the app. Each tap will cycle through 4 different length strings for the label:
class ViewController: UIViewController {
#IBOutlet var label: UILabel!
let strs: [String] = [
"Very Short",
"A little longer, but still one line.",
"Button - You can set the title, image, and other appearance properties of a button.",
"UIStackView - creates and manages the constraints necessary to create horizontal or vertical stacks of views. It will dynamically add and remove its constraints to react to views being removed or added to its stack. With customization it can also react and influence the layout around it.",
]
var idx: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
label.text = strs[idx % strs.count]
// tap anywhere in the view
let t = UITapGestureRecognizer(target: self, action: #selector(gotTap(_:)))
view.addGestureRecognizer(t)
}
#objc func gotTap(_ g: UITapGestureRecognizer) -> Void {
idx += 1
label.text = strs[idx % strs.count]
}
}
and the result will be:

How do I offset UILabel text alignment?

I am trying to offset the center starting point for a UILabel. The problem that I am facing is that I can't make the label text to grow from a point that is offset from center until it reaches one end of the label. Then, I want the text to shift one character to the left with each additional added character until it reaches the the other end. Then it would be acceptable for the text to truncate with an ellipses.
So far, my code looks like this but I don't know where to go from here.
private let amountLabel: UILabel = {
let label = UILabel()
label.textColor = .blue
label.textAlignment = .center
label.translatesAutoresizingMaskIntoConstraints = false
return label
}()
I suggest you put the UILabel into a container view, center horizontally with the offset you want, but set its priority lower than the compression resistance., e.g. 750, compression resistance 999. Then create a trailing constraint >= with priority 1000, and leading constraint >= priority 1000. In that way centering will be the weakest constraint and as the text grows it will shift to the left until it reaches the leading.

Force a view to take as much space as possible inside UIStackView

I'm creating part of my application's UI with Swift and the problem I'm facing is I have a UIStackView with 3 sub views: 2 UILabels and an UIImageView. Here is my first code
let switchview = UISwitch()
let nodelableview = UILabel()
nodelableview.textAlignment = NSTextAlignment.right
nodelableview.numberOfLines = 0
nodelableview.text = nodes[i].type + " " + nodes[i].node_name
let statLabel = UILabel()
statLabel.textAlignment = NSTextAlignment.left
statLabel.text = nodes[i].stat
let stack = UIStackView()
stack.axis = .horizontal
stack.spacing = 16
stack.addArrangedSubview(statLabel)
stack.addArrangedSubview(nodelableview)
stack.addArrangedSubview(switchview)
cell.nodesView.addArrangedSubview(stack)
the problem with this code is that when the nodelabelview has long text the UIStackView not extending to make space for 2 or more lines. So I set the alignment to .center and here is the result
There is empty space left but the first UILabel is using it for nothing. How can I force the second UILabel to use available spaces?
A setup that would give priority to your second label (the one with unlimited number of lines), would be a stackview set to "Fill Proportionally" distribution (which means that views are sized based on their intrinsic size & hugging/resistance priorities)
combined with a horizontal "Content Compression Resistance Priority" of 1000 ('required') for the left label & the switch (which means 'do not compress')
which is resolved to this:
You may need to set the horizontal contentHuggingPriority and contentCompressionResistance for each label / switch to something different from the others, ensuring that the one you wish to expand to fill remaining available space has the lowest hugging value.

UILabel breaks early inside UIStackView

Given the view hierarchy:
UIStackView
--UILabel
--UISwitch
The label breaks too early, even if it can be fit to a single line.
Setting numberOfLines = 1 forces the label to be laid out correctly.
How to make UILabel perform line break only when needed?
Code:
private lazy var title: UILabel = {
let v = UILabel()
v.numberOfLines = 0
return v
}()
private lazy var toggle = UISwitch()
private lazy var stack = UIStackView(axis: .horizontal,
distribution: .equalSpacing,
alignment: .center,
views: [title,
toggle])
func setupConstraints() {
stack.snp.makeConstraints { (make) in
make.edges.equalTo(contentView.layoutMarginsGuide)
}
}
Result:
Setting numberOfLines = 1 gets me what I'd like to achieve, but the label looses its multi-line functionality:
How to force the desired behavior without losing support for multi-line labels?
When there is a lot of horizontal space, the label gets laid out correctly no matter of the numberOfLines property:
Set your UISwitch's content hugging and resistance priority to 1000.
And stack view distribution and alignment to fill.
Extra Note - If you want label and switch to be top aligned, then set alignment to top.
In your stack view you can give a constraint to your label and switch...
1) give your label leading, top , trailing and bottom constraint. Don't give Width constraint. In trailing constraint take second item Switch.
2) give your switch trailing, top, bottom and Fix width.
Hope it Will work.
Add label inside another stack view.
UIStackView
--UIStackView
--UILabel
--UISwitch

Self sizing cells with three UILabels not working

This is the UITableViewCell I have:
The three UILabels have trailing, top, bottom and leading constraints.
This is the hugging priority and compression resistance priority for the UILabel Name:
This is the hugging priority and compression resistance priority for the UILabel Location:
This is the hugging priority and compression resistance priority for the UILabel Type:
On the viewDidLoad from my UITableViewController I'm doing something like this:
self.tableView.estimatedRowHeight = 80
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.reloadData()
But when I run the app, the UITableViewCells don't self sizing:
What am I doing wrong or what do I need to do to make the UITableViewCells make them self sizing?
Edit
I set to 0 the numberOfLines for each UILabel, now the location UILabel doesn't appear
It is most likely that your Text turned out larger in runtime causing the total height in your cell View to turn out greater than the row height itself.
First equate all the hugging priority and then try changing the spacing distance between the Labels with the relation 'Greater than or Equal' and then set the constant to something small like zero. If you still get a constraint error, increase your tableView row height.
Alternative method:
Add all 3 UILabels into another UIView with spacing zero between them, do not set a height constraint for this UIView and just set it in the centerY of the cellView and spaced from the ImageView
Tip: Only change constraint priorities if your UI is what you want even after the constraints are broken, unless of course you know what you're doing
For ur case no need to vertical hugging priority,just give
label.numberOfLines = 0
that works fine.
The question says you want to resize the label, but based on your code it seems like you want to do it by resizing the tableview rows. If that's the case, the correct place to set row height in a tableView is:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return 80
}
Where "return 80" means you want table rows to be of height 80. Please let me know if I didn't understand your question right.
The label doesn't work because the storyboard is not very useful.
Set the var:
var imageView: UIImageView ={
let image = UIImageView()
image.translatesAutoresizingMaskIntoConstraints = false
return image
}()
var textView: UITextView ={
let text = UITextView()
text.translatesAutoresizingMaskIntoConstraints = false
return text
}()
And you continue with other label.
You have to set constraints like this example:
self.addSubview(imageView)
self.addSubview(textView)
imageView.bottomAnchor.constraintEqualToAnchor(self.bottomAnchor, constant: -10).active=true
imageView.leftAnchor.constraintEqualToAnchor(self.leftAnchor, constant:-10).active=true
imageView.widthAnchor.constraintEqualToConstant(70).active=true
imageView.heightAnchor.constraintEqualToConstant(70).active=true
label.topAnchor.constraintEqualToAnchor(self.topAnchor, constant: -5).active=true
label.leftAnchor.constraintEqualToAnchor(imageView.rightAnchor, constant: 3).active=true
label.widthAnchor.constraintEqualToAnchor(self.widthAnchor, constant -70).active=true
label.heightAnchor.constraintEqualToConstant(30).active=true
label2.topAnchor.constraintEqualToAnchor(label.bottomAnchor, constant: 5).active=true
label2.leftAnchor.constraintEqualToAnchor(imageView.rightAnchor, constant: 3).active=true
label2.widthAnchor.constraintEqualToAnchor(self.widthAnchor, constant -70).active=true
label2.heightAnchor.constraintEqualToConstant(30).active=true
And with this way also for the third label.

Resources