How to set UIView height equal to max of two subviews UILabel? - ios

I have to UILabel with dynamic height. I want to set it superview height equal to max of UILabel heights.
class ComponentCell: UIView {
private lazy var leftRow: UILabel = UILabel()
private lazy var rightRow: UILabel = UILabel()
init(leftValue: String, rightValue: String) {
super.init(frame: .zero)
leftRow.backgroundColor = .red
leftRow.numberOfLines = 0
leftRow.lineBreakMode = .byWordWrapping
leftRow.text = leftValue
rightRow.text = rightValue
rightRow.backgroundColor = .yellow
rightRow.numberOfLines = 0
rightRow.lineBreakMode = .byWordWrapping
self.addSubview(self.leftRow)
self.addSubview(self.rightRow)
leftRow.sizeToFit()
rightRow.sizeToFit()
leftRow.setContentHuggingPriority(.required, for: .vertical)
rightRow.setContentHuggingPriority(.required, for: .vertical)
self.translatesAutoresizingMaskIntoConstraints = false
self.leftRow.snp.makeConstraints { make in
make.top.equalToSuperview()
make.left.equalToSuperview()
make.width.equalToSuperview().dividedBy(2)
}
self.rightRow.snp.makeConstraints { make in
make.top.equalToSuperview()
make.right.equalToSuperview()
make.width.equalToSuperview().dividedBy(2)
}
self.layoutIfNeeded()
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
If I set leftRow.botton.equalTo(superview.bottom) and rightRow.botton.equalTo(superview.bottom) it's working. But I think is not a good way. And I don't understand why setContentHuggingPriority not helped me to solve this problem.

Content Hugging
Content hugging leads more likely to squeeze your labels. What you want is the height of the labels to be more respected. So you'd rather use compression resistance priority. However you actually need neither of those.
Layout Constraints
Since you're setting your constraints programatically, you'll need to set translatesAutoresizingMaskIntoConstraints to false for your labels as well:
leftRow.translatesAutoresizingMaskIntoConstraints = false
rightRow.translatesAutoresizingMaskIntoConstraints = false
The bottom constraint is actually a good start, but you don't want to fit the height of the smaller label unnecessarily to the height of the bigger label. So you would want to add a constraint that is "less than or equal to the bottom anchor":
make.bottom.lessThanOrEqualTo(self.snp.bottom)
Lazy Variables
If you want to use lazy variables you'll have to change the way there being initialized. The way you've written it, it initializes the variables right away when initializing the class. But you only want them to be initialized when they're used the first time. For that you need to write it like this:
private lazy var leftRow: UILabel = {
return UILabel()
}()
private lazy var rightRow: UILabel = {
return UILabel()
}()
However in your case you don't need lazy loading, so you can initialize them directly:
private let leftRow = UILabel()
private let rightRow = UILabel()
Other
Since you're using layout constraints, you don't need to call sizeToFit on the labels. It doesn't do anything.
Calling layoutIfNeeded() within the init doesn't do anything either since it will be called anyway once you add ComponentCell as a subview to another view.

Related

How to put two labels horizontally by aligning their top edges in iOS?

I want to create custom view like below.
As you see, it consists from title and price labels. Title can have million number of lines, but its top edge should be aligned with price label. It seems simple design, but it has hundreds of solution. I tried every of them, but my title label is not growing, by having dots at the end (numberOfLines = 0 doesn't help). Here is how I approached to create such a design:
I created titleLabel with top, leading, trailing to price label, bottom constraints. Also, I created price label with top and trailing constraints only in order to align their top edges. I assigned compression resistance and hugging priority to price label, because it is more important and should not be ruined. Here is code if you want:
addSubview(titleLabel)
addSubview(priceLabel)
titleLabel.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(16)
make.trailing.lessThanOrEqualTo(priceLabel.snp.leading).offset(-8)
make.top.equalToSuperview()
make.bottom.equalToSuperview()
}
priceLabel.snp.makeConstraints { make in
make.trailing.equalToSuperview().offset(-16)
make.top.equalTo(titleLabel.snp.top)
}
I created separate custom view, because I want to use it inside StackView(spacing 8, distribution fill, vertical). Result of this approach: title label's is not growing. It has only one line with dots at the end, if it has big text.
Second approach was to create stackView (horizontally, spacing 8, distribution fill, alignment top). I set alignment top in order to align top edges of the labels. The result was the as in approach #1.
How to solve this problem? Where am I wrong? It seems I don't see something core in Auto Layout theory here.
Add a new height constraint as well for the title label with
relation: greater than equal to
contant: some constant(might be 20 or something based on your fontsize and content).
I hope this solves your issue
During development of your layout, it can be very helpful to use contrasting colors for element backgrounds... makes it really easy to see what's happening with their frames.
Give this a try...
Custom view class
class NeoCustomView: UIView {
let titleLabel = UILabel()
let priceLabel = UILabel()
override init(frame: CGRect) {
super.init(frame: frame)
commonInit()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
commonInit()
}
func commonInit() -> Void {
titleLabel.translatesAutoresizingMaskIntoConstraints = false
titleLabel.numberOfLines = 0
titleLabel.font = .systemFont(ofSize: 17)
priceLabel.translatesAutoresizingMaskIntoConstraints = false
priceLabel.font = .boldSystemFont(ofSize: 17)
priceLabel.setContentHuggingPriority(.required, for: .horizontal)
priceLabel.setContentCompressionResistancePriority(.required, for: .horizontal)
addSubview(titleLabel)
addSubview(priceLabel)
titleLabel.snp.makeConstraints { make in
make.leading.equalToSuperview().offset(16)
make.trailing.lessThanOrEqualTo(priceLabel.snp.leading).offset(-8)
make.top.equalToSuperview()
make.bottom.equalToSuperview()
}
priceLabel.snp.makeConstraints { make in
make.trailing.equalToSuperview().offset(-16)
make.top.equalTo(titleLabel.snp.top)
}
// use some background colors so we can easily see the frames
backgroundColor = .red
titleLabel.backgroundColor = .yellow
priceLabel.backgroundColor = .green
}
}
Example view controller class - adds another label constrained 4-pts from the bottom of the custom view so we can see everything working:
class NeoViewController: UIViewController {
let testView = NeoCustomView()
let anotherLabel = UILabel()
override func viewDidLoad() {
super.viewDidLoad()
anotherLabel.translatesAutoresizingMaskIntoConstraints = false
anotherLabel.font = .systemFont(ofSize: 15)
anotherLabel.backgroundColor = .blue
anotherLabel.textColor = .white
anotherLabel.textAlignment = .center
anotherLabel.numberOfLines = 0
anotherLabel.text = "This label is constrained 4 points from the bottom of the custom view."
view.addSubview(testView)
view.addSubview(anotherLabel)
testView.snp.makeConstraints { make in
make.leading.trailing.equalTo(view.safeAreaLayoutGuide).inset(16)
make.top.equalTo(view.safeAreaLayoutGuide).offset(40)
}
anotherLabel.snp.makeConstraints { make in
make.top.equalTo(testView.snp.bottom).offset(4)
make.width.equalTo(testView.snp.width)
make.centerX.equalTo(testView.snp.centerX)
}
testView.titleLabel.text = "This is long text for the title label that will word wrap when it needs to."
testView.priceLabel.text = "300$"
}
}
Result (Red is custom view with title and price labels, Blue is a label added and constrained below the custom view):

UIStackView dynamcially add 2 labels next to each other with no extra spacing

Im new to iOS development and Im a bit confused as to how to achieve this.
I have 2 UILabels added to a UIStackView like so:
let horizontalStackView1 = UIStackView(arrangedSubviews: [self.label1, self.label2])
and when I run the app it looks like this:
However, Id like the labels to be next to each other with no spacing in between something like this:
Ive tried setting horizontalStackView1.distribution, horizontalStackView1.alignment etc with no luck.
How do I achieve this?
Thanks in advance!
UPDATE:
The code looks like this (its a cell of a table by the way):
class ItemTableViewCell: UITableViewCell
{
...
let stateLabel = UILabel()
let effectiveDateLabel = UILabel()
...
var typeImage: UIImage?
{
...
}
var summary: String?
{
...
}
var effectiveDate: Date?
{
...
}
override init(style: UITableViewCellStyle, reuseIdentifier: String?)
{
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.accessoryType = .disclosureIndicator
...
let horizontalStackView1 = UIStackView(arrangedSubviews: [self.stateLabel, self.effectiveDateLabel])
let horizontalStackView2 = UIStackView(arrangedSubviews: [typeImageViewWrapper, self.titleLabel])
horizontalStackView2.spacing = 4
let verticalStackView = UIStackView(arrangedSubviews: [horizontalStackView1, horizontalStackView2, self.summaryLabel])
verticalStackView.axis = .vertical
verticalStackView.spacing = 4
self.contentView.addSubview(verticalStackView)
...
}
required init?(coder aDecoder: NSCoder)
{
fatalError()
}
}
That's because the UIStackView picks the first arrangedSubview with lowest content hugging priority and resizes it so the stackview's content takes up full width.
If you want to use UIStackView for this case, you can should change the content hugging priorities, eg.
label2.setContentHuggingPriority(.defaultLow, for: .horizontal)
label1.setContentHuggingPriority(.defaultHigh, for: .horizontal)
The stackviews distribution should be set to fillProportionally so every arranged subview keeps its proportions.
However, the remaining space is filled by the stackview automatically. To suppress this, you need to add an empty view at the end. This empty view needs a low content hugging priority so it can grow to fill up the space where the other views remain by their proportions.
Furthermore, the empty view needs an intrinsicContentSize for the stackview to compute the dimensions:
class FillView: UIView {
override var intrinsicContentSize: CGSize {
get { return CGSize(width: 100, height: 100) }
}
}
Now set your arranged subviews and put the fillView at the end
let fillView: UIFillView()
fillView.setContentHuggingPriority(priority: .fittingSizeLevel, for: .horizontal)
myStackView.addArrangedSubview(fillView)
Set the stackviews spacing to your needs to maintain the distance between the subviews.

How to add SubView to a horizontal StackView programmatically to create Stripes?

I have a collectionView Cell with a StackView in it.
class OuterCollectionViewCell: UICollectionViewCell {
#IBOutlet weak var stackView: UIStackView!
override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
for i in 1...30 {
let tick = UIView()
if i % 2 == 0 {
tick.backgroundColor = UIColor.red
} else {
tick.backgroundColor = UIColor.clear
}
tick.tag = i
tick.heightAnchor.constraint(equalTo: self.heightAnchor, constant: 0.0).isActive = true
self.stackView?.insertArrangedSubview(tick, at:i)
}
}
I want to draw 30 cells on the stackView alternating between red and clear . Right now I am setting the index of the subview as well as the height with a auto layout anchor constraint and hoping that the .fillEqually distribution will compute the width and X locations of each of the subviews. However right now, it crashes when I try and set the height anchor and simply doesn't show anything when I remove the height anchor. How do I lay these out such that they are full height, and right next to each other with no margins?
I think that you shouldn't be adding a height anchor here to items within your stackview. Automatically, things contained in the stack view are equal heights (or widths depending on stackview orientation). You shouldn't need to be setting sizes within the stackview directly. It defeats the purpose of the stackview in the first place.

Programmatically created horizontal UIStackView with 2 justified labels

I want 2 labels (say leftLabel, rightLabel) and place them horizontally such that leftLabel stretches and rightLabel just fits single character icon (say, ">"). Thus both labels layout justified. Like this...
This is the code I have -
class StackViewController: UIViewController {
/// Main vertical outer/container stack view that pins its edges to this view in storyboard (i.e. full screen)
#IBOutlet weak private var containerStackView: UIStackView!
private var leftLabel: UILabel = {
let leftLabel = UILabel(frame: .zero)
leftLabel.font = .preferredFont(forTextStyle: .body)
leftLabel.numberOfLines = 0 // no text truncation, allows wrap
leftLabel.backgroundColor = .orange
return leftLabel
}()
private var rightLabel: UILabel = {
let rightLabel = UILabel(frame: .zero)
rightLabel.font = .preferredFont(forTextStyle: .body)
// Set CHCR as high so that label sizes itself to fit the text
rightLabel.setContentHuggingPriority(UILayoutPriorityDefaultHigh, for: .horizontal)
rightLabel.setContentCompressionResistancePriority(UILayoutPriorityDefaultHigh, for: .horizontal)
rightLabel.backgroundColor = .green
return rightLabel
}()
override func viewDidLoad() {
super.viewDidLoad()
prepareAndLoadSubViews()
// Note, the text required to be set in viewDidAppear, not viewDidLoad, otherwise rightLabel stretches to fill!!
leftLabel.text = "This is left label text that may go in multiple lines"
rightLabel.text = ">" // Always a single character
}
/// Dynamically creates a horizontal stack view, with 2 labels, in the container stack view
private func prepareAndLoadSubViews() {
/// Prepare the horizontal label stack view and add the 2 labels
let labelStackView = UIStackView(arrangedSubviews: [leftLabel, rightLabel])
labelStackView.axis = .horizontal
labelStackView.distribution = .fillProportionally
labelStackView.alignment = .top
containerStackView.addArrangedSubview(labelStackView)
containerStackView.addArrangedSubview(UIView())
}
}
Which gives below result (i.e. leftLabel width is 0 in view debugger) -
NOTE: If I move text set code in viewDidAppear then it works fine.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
// Note, the text required to be set in viewDidAppear, not viewDidLoad, otherwise rightLabel stretches to fill!!
leftLabel.text = "This is left label text that may go in multiple lines"
rightLabel.text = ">" // Always a single character
}
Why?
And, can we set content hugging/ compression resistance priorities before viewDidLoad?
I played around with your code quite a bit but I was not able to make it work either. I think this is a bug that occurs when you add a UIStackView to another UIStackView. When you only have one UIStackView your code works fine.
So I cannot offer a fix for your case but IMHO you shouldn't really need to use a UIStackView for your 2 labels at all. UIStackView is great if you have multiple arranged subviews that you hide and show and need to be arranged automatically. For just two "static" labels I think it is a bit of an overkill.
You can achieve what you are after by adding your two labels to a UIView and then set layout constraints to the labels. It's really easy:
class StackViewController: UIViewController {
#IBOutlet weak var containerStackView: UIStackView!
private var leftLabel: UILabel = {
let leftLabel = UILabel(frame: .zero)
leftLabel.font = .preferredFont(forTextStyle: .body)
leftLabel.numberOfLines = 0
leftLabel.backgroundColor = .orange
leftLabel.translatesAutoresizingMaskIntoConstraints = false
leftLabel.numberOfLines = 0
return leftLabel
}()
private var rightLabel: UILabel = {
let rightLabel = UILabel(frame: .zero)
rightLabel.font = .preferredFont(forTextStyle: .body)
rightLabel.setContentHuggingPriority(UILayoutPriority.required, for: .horizontal)
rightLabel.backgroundColor = .green
rightLabel.translatesAutoresizingMaskIntoConstraints = false
return rightLabel
}()
override func viewDidLoad() {
super.viewDidLoad()
prepareAndLoadSubViews()
leftLabel.text = "This is left label text that may go in multiple lines"
rightLabel.text = ">"
}
private func prepareAndLoadSubViews() {
let labelContainerView = UIView()
labelContainerView.addSubview(leftLabel)
labelContainerView.addSubview(rightLabel)
NSLayoutConstraint.activate([
leftLabel.leadingAnchor.constraint(equalTo: labelContainerView.leadingAnchor),
leftLabel.topAnchor.constraint(equalTo: labelContainerView.topAnchor),
leftLabel.bottomAnchor.constraint(equalTo: labelContainerView.bottomAnchor),
rightLabel.leadingAnchor.constraint(equalTo: leftLabel.trailingAnchor),
rightLabel.topAnchor.constraint(equalTo: labelContainerView.topAnchor),
rightLabel.bottomAnchor.constraint(equalTo: labelContainerView.bottomAnchor),
rightLabel.trailingAnchor.constraint(equalTo: labelContainerView.trailingAnchor)
])
containerStackView.addArrangedSubview(labelContainerView)
containerStackView.addArrangedSubview(UIView())
}
}

iOS AutoLayout working with something wrong

I add a UIView in UIStoryboard and bind it to a custom UIView class called testView,next, I create a UIView called circelView in textView in require init function ,then I crate two UILabel in circelView
this is my step
initialization the circelView
add AutoLayout to circelView
create two UILabel and add them to circelView
add AutoLayout to two UILabel
then I run the app, I can find the subView in current position , But I can not find the two UILabel in screen ,what happen?
this is my code :
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
circelView = UIView()
self.addSubview(circelView)
circelView.snp_makeConstraints(closure: { (make) -> Void in
make.size.equalTo(80)
make.top.equalTo(self.snp_top)
make.right.equalTo(self.snp_right)
})
titleLabel = UILabel()
titleLabel.textColor = colorffffff
titleLabel.backgroundColor = UIColor.yellowColor()
titleLabel.font = font32
titleLabel.text = "hello"
titleLabel.numberOfLines = 1
titleLabel.backgroundColor = UIColor.yellowColor()
titleLabel.textAlignment = NSTextAlignment.Center
circelView.addSubview(titleLabel)
titleLabel.snp_makeConstraints { (make) -> Void in
make.center.equalTo(circelView.snp_center)
}
detailLabel = UILabel()
detailLabel.textColor = colorffffff
detailLabel.font = font24
detailLabel.text = "hello"
titleLabel.numberOfLines = 1
detailLabel.textAlignment = NSTextAlignment.Center
detailLabel.addSubview(titleLabel)
detailLabel.snp_makeConstraints { (make) -> Void in
make.top.equalTo(titleLabel.snp_bottom).offset(3)
make.centerX.equalTo(titleLabel.snp_centerX)
}
}
A few things could be causing this:
How big is your font? If your font is too large, and your view is too small (80x80 in this case), the text may not be rendered.
In Autolayout, it is optimal to have views have sizes based on their content, and not explicit (i.e. 80x80). By having your labels be pinned to their superview on all edges, you can have the size of your 'circelView' be dynamic to its content (the two labels). To maintain the 'circle' aspect of your 'circelView', you can constraint the height of your 'circelView' to be the same as its width.
At the end of the day, this needs more debugging. I advise examining the view hierarchy to see where your labels are. Check out the documentation here.
Your labels have 0 width and height. You don't set their frame anywhere in this code, and the constraints which apply to them relate only to their position, no size can be determined from them. You either need to Specify their frame or add constraints from which it could be determined by layouting engine.

Resources