Multiline Label in Uistackview

Multiline label in UIStackView

The correct answer is here:

https://stackoverflow.com/a/43110590/566360


  1. Embed the UILabel inside a UIView (Editor -> Embed In -> View)
  2. Use constraints to fit the UILabel to the UIView (for example, trailing space, top space, and leading space to superview constraints)

The UIStackView will stretch out the UIView to fit properly, and the UIView will constrain the UILabel to multiple lines.

Horizontal UIStackView with two label (one multiline label, one one-line)

To try and simplify...

Forget calculating any widths... what matters is the horizontal Content Hugging and Content Compression Resistance

Leave the left (blue) label at the defaults:

Content Hugging Priority
Horizontal: 251

Content Compression Resistance Priority:
Horizontal: 750

But set the right (orange) label to:

Content Hugging Priority
Horizontal: 1000

Content Compression Resistance Priority:
Horizontal: 1000

Results:

Sample Image

The only other issue would be if the text in the right-side label exceeds the full width of the view -- but you haven't indicated that you might need that much text.

UIStackView and Multiline Labels?

This really seems to be a bug.

A workaround that works for me is embedding the multiline label in a view and leaving it there.

That fixes the layout on the Storyboard editor and also works in the simulator.

A weird thing is that if I have several multiline labels on the same StackView I only have to embed one of them in a UIView, and then all the other multiline labels will behave properly.

UIStackView distribution and alignment of a multiline UILabel

UPDATE

Since you're setting titleLabel.numberOfLines = 3, one way to do this is simply to append three newlines to the title text. That will force titleLabel to always consume its full height of three lines, forcing timeLabel to the bottom.

That is, when you set titleLabel.text, do it like this:

titleLabel.text = theTitle + "\n\n\n"

ORIGINAL

If you let one of the labels stretch vertically, the stretched label's text will be centered vertically within the stretched label's bounds, which is not what you want. So we can't let the labels stretch vertically. Therefore we need to introduce a padding view that can stretch but is otherwise invisible.

If the padding view gets squeezed down to zero height, the stack view will still put spacing before and after it, leading to double-spacing between titleLabel and timeLabel, which you also don't want.

So we'll need to implement all the spacing using padding views. Change verticalStackView.spacing to 0.

Add a generic UIView named padding1 to verticalStackView after categoryLabel, before titleLabel. Constrain its height to equal 10.

Add a generic UIView named padding2 to verticalStackView after titleLabel, before timeLabel. Constrain its height to greater than or equal to 10 so that it can stretch.

Set the vertical hugging priorities of categoryLabel, titleLabel, and timeLabel to required, so that they will not stretch vertically.

Constrain the height of verticalStackView to the height of containerStackView so that it will stretch one or more of its arranged subviews if needed to fill the vertical space available. The only arranged subview that can stretch is padding2, so it will stretch, keeping the title text near the top and the time text at the bottom.

Also, constrain your containerStackView to the bounds of contentView and set containerStackView.translatesAutoresizingMaskIntoConstraints = false.

Result:

short title

long title

Here's my playground:

import UIKit
import PlaygroundSupport

class MyCell: UICollectionViewCell {

var containerStackView: UIStackView!
var verticalStackView: UIStackView!
var categoryLabel: UILabel!
var titleLabel: UILabel!
var timeLabel: UILabel!
var imageView: UIImageView!

func createSubViews() {

// contains the UIStackview with the 3 labels and the UIImageView
containerStackView = UIStackView()
containerStackView.axis = .horizontal
containerStackView.distribution = .fill
containerStackView.alignment = .top
contentView.addSubview(containerStackView)

// the UIStackView for the labels
verticalStackView = UIStackView()
verticalStackView.axis = .vertical
verticalStackView.distribution = .fill
verticalStackView.spacing = 0
containerStackView.addArrangedSubview(verticalStackView)

categoryLabel = UILabel()
categoryLabel.font = UIFont.preferredFont(forTextStyle: .caption1)
categoryLabel.textColor = UIColor.lightGray
verticalStackView.addArrangedSubview(categoryLabel)

let padding1 = UIView()
verticalStackView.addArrangedSubview(padding1)

titleLabel = UILabel()
titleLabel.numberOfLines = 3
titleLabel.lineBreakMode = .byWordWrapping
verticalStackView.addArrangedSubview(titleLabel)

let padding2 = UIView()
verticalStackView.addArrangedSubview(padding2)

timeLabel = UILabel()
timeLabel.font = UIFont.preferredFont(forTextStyle: .caption1)
timeLabel.textColor = UIColor.lightGray
verticalStackView.addArrangedSubview(timeLabel)

// UIImageView
imageView = UIImageView()
imageView.contentMode = .scaleAspectFill
imageView.clipsToBounds = true
imageView.layer.cornerRadius = 5
layer.masksToBounds = true
containerStackView.addArrangedSubview(imageView)

categoryLabel.setContentHuggingPriority(.required, for: .vertical)
titleLabel.setContentHuggingPriority(.required, for: .vertical)
timeLabel.setContentHuggingPriority(.required, for: .vertical)
imageView.setContentHuggingPriority(.defaultHigh, for: .horizontal)
containerStackView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
contentView.leadingAnchor.constraint(equalTo: containerStackView.leadingAnchor),
contentView.trailingAnchor.constraint(equalTo: containerStackView.trailingAnchor),
contentView.topAnchor.constraint(equalTo: containerStackView.topAnchor),
contentView.bottomAnchor.constraint(equalTo: containerStackView.bottomAnchor),
verticalStackView.heightAnchor.constraint(equalTo: containerStackView.heightAnchor),
padding1.heightAnchor.constraint(equalToConstant: 10),
padding2.heightAnchor.constraint(greaterThanOrEqualToConstant: 10),
])
}
}

let cell = MyCell(frame: CGRect(x: 0, y: 0, width: 320, height: 110))
cell.backgroundColor = .white
cell.createSubViews()
cell.categoryLabel.text = "MY CUSTOM LABEL"
cell.titleLabel.text = "This is my title"
cell.timeLabel.text = "3 days ago"
cell.imageView.image = UIGraphicsImageRenderer(size: CGSize(width: 110, height:110)).image { (context) in
UIColor.blue.set()
UIRectFill(.infinite)
}

PlaygroundPage.current.liveView = cell

Horizontal StackView issue for multiline label and fixed size icon image

Working Constraints.!

  1. Set inner Stack view Alignment to Top and Distribution set to
    Fill Proportionally
  2. Set width of icon to fixed of 20px(No Aspect ratio and no
    Height)
  3. Set icon Content Compression Resistance property Horizontal
    priority to 1000.

Images attached

Sample Image

Sample Image

iOS: UILabel multi-line text get truncated in horizontal UIStackView

alignment

/* The layout of the arrangedSubviews transverse to the axis;
e.g., leading/trailing edges in a vertical stack
*/
@property(nonatomic) UIStackViewAlignment alignment;

For .fill

StackView will automatically generate these layout attributes for you.

 imageView.bottom == label.bottom
imageView.top == label.top

the size of stackView depend on the 'voice' image's size or size layout attributes of imageView as it is fixed instead of adjustable UILable which namely have the low priority.


For .center

StackView achieve a decent height by look for the max height between your label and imageView.

 _UILayoutSpacer.bottom >= imageView.bottom
_UILayoutSpacer.bottom >= label.bottom
_UILayoutSpacer.top <= imageView.top
_UILayoutSpacer.top <= label.top


Related Topics



Leave a reply



Submit