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:


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