How to contain overflowed UILabel's text inside UIView?

Solution 1:

Here are my thoughts on what I think is happening.

I will start with the padding bit. I believe both these cases are actually the same:

UILabel Centering AutoLayout

The default behaviour is to center the text and in both cases this is happening. In the second case (image on the right) which you say there is a padding, the UILabel cannot fit another line of text at that font size and so it stops at line 4 and centers the text with the label's frame.

If you increase the label height to 500 for example, you will see that it is not a random padding, but rather a centering.

Here is a simple approximation of how many lines of text a given height might support.

let label = UILabel()
label.text = "This is my text.This is my text.This is my text.This is my text.This is my text.This is my text.This is my text."
label.numberOfLines = 0
label.sizeToFit()

// I added these line
print("Max lines that will fit: \(floor(100.0 / label.font.lineHeight))")
print("Max lines that will fit: \(floor(130.0 / label.font.lineHeight))")

The answer printed out is 4.0 and 6.0. As I increased your label height to 130 as follows, and it gave me 6 lines as approximated:

// Inside your set up function
container.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
    container.leadingAnchor.constraint(equalTo: view.leadingAnchor),
    container.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
    container.heightAnchor.constraint(equalToConstant: 130.0),
    container.widthAnchor.constraint(equalToConstant: 100.0)
])

let label = UILabel()
label.text = "This is my text.This is my text.This is my text.This is my text.This is my text.This is my text.This is my text."
label.numberOfLines = 0
label.sizeToFit()

UILabel size top align autolayout

So I hope that explains the bit about the padding.

Now coming to how to prevent the overflow, I think your autolayout already prevents that. I think also adding clipsToBounds on the container will prevent the label from overflowing.

Finally, aligning the label to the top is a different question altogether. However there was a wonderful solution I came across here which was subclassing a UILabel and overriding drawRect like so:

// Credit to Ma11hew28 https://stackoverflow.com/a/41367948/1619193
class TopAlignedLabel: UILabel {
  override func drawText(in rect: CGRect) {
    let textRect = super.textRect(forBounds: bounds, limitedToNumberOfLines: numberOfLines)
    super.drawText(in: textRect)
  }
}

// Your new set up function will be like this, I have commented
// the 2 changes I made
private func setup() {
    let container = UIView()
    container.backgroundColor = .red
    view.addSubview(container)
    
    container.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activate([
        container.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
        container.heightAnchor.constraint(equalToConstant: 100.0),
        container.widthAnchor.constraint(equalToConstant: 100.0)
    ])
    
    // Use top aligned label
    let label = TopAlignedLabel()
    label.text = "This is my text."
    label.numberOfLines = 0
    label.sizeToFit()
    
    container.addSubview(label)
    
    label.translatesAutoresizingMaskIntoConstraints = false
    NSLayoutConstraint.activate([
        label.widthAnchor.constraint(equalTo: container.widthAnchor)
    ])
    
    // Clip container to bounds to prevent overflow
    container.clipsToBounds = true
}

Final version prevents overflow and aligns text to the top when there is less text:

UILabel Align text to the top vertically