How to make a horizontal StackView to have the first element's width and fill the rest of it - ios

I'm new with swift and trying to create an input field at the moment. My problem is, that I would like to have a Label as shown in the picture:
So far, I'm working with StackViews: One vertical one for the input fields, and three horizontal ones to have the Title and the user input. My code so far is as follows:
// Initialize outter stackview
let feedbackOutterSV = UIStackView()
view.addSubview(feedbackOutterSV)
feedbackOutterSV.translatesAutoresizingMaskIntoConstraints = false
feedbackOutterSV.axis = NSLayoutConstraint.Axis.vertical
NSLayoutConstraint.activate([
feedbackOutterSV.topAnchor.constraint(equalTo: tutorialText.bottomAnchor, constant: 10),
feedbackOutterSV.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
feedbackOutterSV.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
feedbackOutterSV.heightAnchor.constraint(equalToConstant: 300)
])
// Initalize inner stackview for title
let feedbackInnerSVTitle = UIStackView()
feedbackOutterSV.addArrangedSubview(feedbackInnerSVTitle)
feedbackInnerSVTitle.translatesAutoresizingMaskIntoConstraints = false
feedbackInnerSVTitle.axis = .horizontal
feedbackInnerSVTitle.alignment = .fill
feedbackInnerSVTitle.distribution = .fillProportionally
let titleLabel = UILabel()
feedbackInnerSVTitle.addArrangedSubview(titleLabel)
titleLabel.translatesAutoresizingMaskIntoConstraints = false
titleLabel.text = "feedback.input.title".localize()
titleLabel.font = UIFont.preferredFont(forTextStyle: .body)
titleLabel.textColor = .gray
let titleTextView = UITextView()
feedbackInnerSVTitle.addArrangedSubview(titleTextView)
titleTextView.translatesAutoresizingMaskIntoConstraints = false
titleTextView.font = UIFont.preferredFont(forTextStyle: .body)
titleTextView.isScrollEnabled = false
NSLayoutConstraint.activate([
titleLabel.widthAnchor.constraint(equalToConstant: 39)
])
This code gives the expected output for English, however I have to implement it in different languages, so I can't use a constant width.
Can anyone tell me how to change my code, so I don't need the constant constraint but the width of the Label is adjusted to the length of the word?
Thanks in advance

Couple things...
I assume you want the "title label" to be top-aligned with your textView, so change .fill to .top:
feedbackInnerSVTitle.alignment = .top // .fill
and, don't use .fillProportionally
feedbackInnerSVTitle.distribution = .fill // .fillProportionally
Now, you'll likely see each element taking 50% of the width, so change the content hugging priority for your title label:
titleLabel.setContentHuggingPriority(.required, for: .horizontal)
and, finally, don't set a width constraint on your title label:
// NSLayoutConstraint.activate([
// titleLabel.widthAnchor.constraint(equalToConstant: 39)
// ])
Result:

In your code, width constraint on titleLabel must be set to titleLabel.intrinsicContentSize.width
NSLayoutConstraint.activate([
titleLabel.widthAnchor.constraint(equalToConstant: titleLabel.intrinsicContentSize.width)
])
Also, set the distribution of feedbackInnerSVTitle as .fill
feedbackInnerSVTitle.distribution = .fill

I think you could use NSLayoutConstraint.activate([
titleLabel.widthAnchor.constraint(greaterThanOrEqualToConstant: 0)] to let it grow depending on the content

Related

Control the Size of TextField in UIKit Application

I am using the following to add a UITextField to a UIStackView. The main issue is the UITextField is expanding to take the complete height. What am I doing wrong? I want UITextField to be of 44 or 60 points height.
lazy var nameTextField: UITextField = {
let textfield = UITextField()
textfield.translatesAutoresizingMaskIntoConstraints = false
textfield.placeholder = "Budget name"
textfield.leftView = UIView(frame: CGRect(x: 0, y: 0, width: 10, height: 0))
textfield.leftViewMode = .always
textfield.borderStyle = .roundedRect
return textfield
}()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.green
setupUI()
}
private func setupUI() {
let stackView = UIStackView()
stackView.alignment = .leading
stackView.translatesAutoresizingMaskIntoConstraints = false
stackView.axis = .vertical
stackView.spacing = UIStackView.spacingUseSystem
stackView.isLayoutMarginsRelativeArrangement = true
stackView.directionalLayoutMargins = NSDirectionalEdgeInsets(top: 20, leading: 20, bottom: 20, trailing: 20)
view.addSubview(stackView)
stackView.addArrangedSubview(nameTextField)
// add constraints on nameTextField
nameTextField.widthAnchor.constraint(equalToConstant: 200).isActive = true
nameTextField.heightAnchor.constraint(equalToConstant: 60).isActive = true
// add constraints stackview
stackView.widthAnchor.constraint(equalTo: view.widthAnchor).isActive = true
stackView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
}
}
A UIStackView arranges its subviews (.addArrangedSubview()).
So, you are telling auto-layout to:
make the text field 60-points tall
AND
make the text field as tall as the stack view
In this case, the stack view wins.
Edit - for clarification...
When you ran your app, you should have seen a bunch of auto-layout error / warning messages. That tells you that you have assigned conflicting constraints.
If you want the text field height to use the .heightAnchor.constraint(equalToConstant: 60) that you've assigned, you have a few options...
1 - Don't embed it in a stack view.
2 - Don't assign a height to the stack view (either directly or with top & bottom constraints).
3 - add additional arrangedSubviews to the stack view.
So, if you make only this change to your code:
stackView.addArrangedSubview(nameTextField)
// comment out this line
//stackView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
you'll get this:
If you leave that line in, and add a yellow-background UILabel as another arranged subview:
stackView.addArrangedSubview(nameTextField)
// leave this un-commented
stackView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
let label = UILabel()
label.text = "The Label"
label.backgroundColor = .yellow
label.widthAnchor.constraint(equalToConstant: 200).isActive = true
stackView.addArrangedSubview(label)
you'll get this:
because you gave the text field an explicit Height constraint, so the label height "stretches."
Or, if you add the label and omit the stack view's bottom anchor:
stackView.addArrangedSubview(nameTextField)
// comment out this line
//stackView.heightAnchor.constraint(equalTo: view.heightAnchor).isActive = true
let label = UILabel()
label.text = "The Label"
label.backgroundColor = .yellow
label.widthAnchor.constraint(equalToConstant: 200).isActive = true
stackView.addArrangedSubview(label)
you'll get this:
because you gave the text field an explicit Height constraint, and let the label use its Intrinsic Content Size.

How to correctly add safe area inset to UIStackView?

So I have a simple stack view with two text inside it and I have added safe area constraints,
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
let label = UILabel()
label.text = "Hello, World!"
label.sizeToFit()
label.translatesAutoresizingMaskIntoConstraints = false
let label2 = UILabel()
label2.text = "Hello, World!"
label2.sizeToFit()
label2.translatesAutoresizingMaskIntoConstraints = false
let stackView = UIStackView()
stackView.axis = .vertical
stackView.addArrangedSubview(label)
stackView.addArrangedSubview(label2)
stackView.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(stackView)
NSLayoutConstraint.activate([
stackView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
stackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
])
}
}
But it has big amount of space between two text. How do I remove that space? This only happens when I add this line stackView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor).
The problem is that the StackView's making it so the elements in it are filling up the space equally.
Try setting: stackView.distribution = .fill
Here's an article about how StackView works and why that's happening to you: https://spin.atomicobject.com/2016/06/22/uistackview-distribution/

UIImageView ignores widthAnchor inside UIStackView

I'm trying to align and scale an image inside a UIStackView:
class LogoLine: UIViewController {
override func viewDidLoad() {
let label = UILabel()
label.text = "powered by"
label.textColor = .label
label.textAlignment = .center
let logoToUse = UIImage(named: "Image")
let imageView = UIImageView(image: logoToUse!)
let stackView = UIStackView(arrangedSubviews: [label, imageView])
stackView.axis = .vertical
stackView.distribution = .fillProportionally
view.addSubview(stackView)
stackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
imageView.widthAnchor.constraint(equalToConstant: 50), // this gets ignored
stackView.topAnchor.constraint(equalTo: self.view.topAnchor),
stackView.leadingAnchor.constraint(equalTo: self.view.leadingAnchor),
stackView.trailingAnchor.constraint(equalTo: self.view.trailingAnchor),
view.heightAnchor.constraint(equalTo: stackView.heightAnchor)
])
}
}
This is how it looks in the simulator (going from border to border):
Question: Why is the UIImageView ignoring my widthAnchor constraint of 50pt, and why does the aspect ratio of the original image get changed? How can I constrain the UIImage (or the UIImageView) to e.g. half the screen width and maintain the aspect ratio?
By default, a vertical stack view has .alignment = .fill ... so it will stretch the arranged subviews to "fill the width of the stack view."
Change it to:
stackView.alignment = .center
As a side note, get rid of the stackView.distribution = .fillProportionally ... it almost certainly is not what you want.

Why does UIStackView not stack a UILabel arrangedSubview?

I have a UIStackView, and as arranged subviews, I have two UIViews, and a UILabel. The UIViews are stacked one after another, while the UILabel is aligned to the leading edge of the subview.
Code:
let stack = UIStackView()
stack.axis = .horizontal
stack.distribution = .fillProportionally
stack.alignment = .center
stack.spacing = 7
view.addSubview(stack)
stack.translatesAutoresizingMaskIntoConstraints = false
stack.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true
stack.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
let icon = UIView()
icon.backgroundColor = UIColor.red
stack.addArrangedSubview(icon)
icon.translatesAutoresizingMaskIntoConstraints = false
icon.heightAnchor.constraint(equalToConstant: 42).isActive = true
icon.widthAnchor.constraint(equalToConstant: 42).isActive = true
let icon2 = UIView()
icon2.backgroundColor = UIColor.red
stack.addArrangedSubview(icon2)
icon2.translatesAutoresizingMaskIntoConstraints = false
icon2.heightAnchor.constraint(equalToConstant: 42).isActive = true
icon2.widthAnchor.constraint(equalToConstant: 42).isActive = true
let label = UILabel()
label.lineBreakMode = .byWordWrapping
label.numberOfLines = 0
label.sizeToFit()
label.translatesAutoresizingMaskIntoConstraints = false
label.backgroundColor = .yellow
label.text = "Hello World! Again"
label.textColor = .black
stack.addArrangedSubview(label)
Output:
For clarity...
First, don't use .fillProportionally. Whatever you think that will do, it's wrong. You have explicitly set your two "icons" to be 42-pts wide each. If the stack view is using .fillProportionally, it will try to change the widths of those views, and you will get auto-layout conflicts.
Second, a UILabel with .numberOfLines = 0 must have a width. Otherwise, there is no way to know where to break the text... with a lot of text, it will extend way off the sides of the view.
Third, the line label.sizeToFit() isn't going to accomplish anything here. Just delete it.
If you add this line:
stack.widthAnchor.constraint(equalToConstant: 200.0).isActive = true
then the stack view will expand to 200-pts wide...
Auto-layout will give the first arranged view a width of 42 (because that's what you declared it to be), and the same for the second view. Since you've set the spacing to 7, it will then calculate
42 + 7 + 42 + 7
which equals 98. It subtracts that from the stack view's width:
200 - 98 = 102
and that is the width it will give your label.
Result:
If you don't want to explicitly set the width to 200, you can set it to a percentage of the superview's width, or give it leading and trailing constraints.
Try to change distribution to fill
stack.distribution = .fill
With multiline set
After commenting it

Divide stackview in three subviews with a dashed divider line?

I'm trying to divide a UIStackView intro three separate subviews and have them divided by a dashed line. I know you can set spacing on a UIStackView but as far as I'm aware you cannot change that spacing to be a dashed line.
Basically I want my three subviews to scale properly on different device sizes but the dashed line to always be small in between them. For clarity the result I'm trying to achieve looks like this:
I hope someone can point me in the right direction, thanks in advance!
You can constrain the 3 views to have equal width, and then add a couple of separator views constrained to a constant width.
let stackView = UIStackView()
stackView.axis = .horizontal
self.view.addSubview(stackView)
let view1 = UIView()
view1.backgroundColor = .red
stackView.addArrangedSubview(view1)
let separator1 = UIView()
separator1.backgroundColor = .black
stackView.addArrangedSubview(separator1)
separator1.widthAnchor.constraint(equalToConstant: 1).isActive = true
let view2 = UIView()
view2.backgroundColor = .green
stackView.addArrangedSubview(view2)
view2.widthAnchor.constraint(equalTo: view1.widthAnchor, multiplier: 1).isActive = true
let separator2 = UIView()
separator2.backgroundColor = .black
stackView.addArrangedSubview(separator2)
separator2.widthAnchor.constraint(equalToConstant: 1).isActive = true
let view3 = UIView()
view3.backgroundColor = .blue
stackView.addArrangedSubview(view3)
view3.widthAnchor.constraint(equalTo: view1.widthAnchor, multiplier: 1).isActive = true

Resources