26
votes

I have a problem with UILabel subclass cutting off text in the bottom. Label is of proper height to fit the text, there is some space left in the bottom, but the text is still being cut off.

The label

The red stripes are border added to label's layer.

I subclass the label to add edge insets.

override func sizeThatFits(size: CGSize) -> CGSize {
    var size = super.sizeThatFits(size)
    size.width += insets.left + insets.right
    size.height += insets.top + insets.bottom
    return size
}

override func drawTextInRect(rect: CGRect) {
    super.drawTextInRect(UIEdgeInsetsInsetRect(rect, insets))
}

However, in this particular case the insets are zero.

7
try to increase UILabel height because the height of label is less than the font size or decrease font size. - Chirag D jinjuwadiya
As you can see, the text height is lower than label's height. Also, if I use regular label instead of this subclass, the text is not cut off. - mag_zbc
@mag_zbc which custom font are you using. - Nimit Parekh
What worked for me was allowing the containing view to become taller, as in Height >= - Daniel Springer

7 Answers

17
votes

Turns out the problem was with

self.lineBreakMode = .ByClipping

changing it to

self.lineBreakMode = .ByCharWrapping

Solved the problem

11
votes

I was facing the same issue with Helvetica Neue Condensed Bold font. Changing label's Baseline property from Align Baselines to Align Centers did the trick for me. You can change this easily in storyboard by selecting your label.

9
votes

My problem was that the label's (vertical) content compression resistance priority was not high enough; setting it to required (1000) fixed it.

It looks like the other non-OP answers may be some sort of workaround for this same underlying issue.

6
votes

TL'DR

Probably the property you are looking for is UILabel's baselineAdjustment.

It is needed because of an old UILabel's known bug. Try it:

label.baselineAdjustment = .none

Also it could be changed through interface builder. This property could be found under UILabel's Attributes inspector with the name "Baseline".

Baseline property on Interface Builder


Explanation

It's a bug

There is some discussions like this one about a bug on UILabel's text bounding box. What we observe here in our case is some version of this bug. It looks like the bounding box grows in height when we shrink the text through AutoShrink .minimumFontScale or .minimumFontSize.

As a consequence, the bounding box grows bigger than the line height and the visible portion of UILabel's height. That said, with baselineAdjustment property set to it's default state, .alignBaselines, text aligns to the cropped bottom and we could observe line clipping.

Understanding this behaviour is crucial to explain why set .alignCenters solve some problems but not others. Just center text on the bigger bounding box could still clip it.


Solution

So the best approach is to set

label.baselineAdjustment = .none

The documentation for the .none case said:

Adjust text relative to the top-left corner of the bounding box. This is the default adjustment.

Since bonding box origin matches the label's frame, it should fix any problem for a one-lined label with AutoShrink enabled.

Also it could be changed through interface builder. This property could be found under UILabel's Attributes inspector with the name "Baseline".


Documentation

You could read more here about UILabel's baselineAdjustmenton official documentation.

3
votes

Happened for me when providing topAnchor and centerYAnchor for label at the same time. Leaving just one anchor fixed the problem.

2
votes

Other answers didn't help me, but what did was constraining the height of the label to whatever height it needed, like so:

let unconstrainedSize = CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude)
label.heightAnchor.constraint(equalToConstant: label.sizeThatFits(unconstrainedSize).height).isActive = true

Also, sizeThatFits(_:) will return a 0 by 0 size if your label's text field is nil or equal to ""

2
votes

I ran into this too, but wanted to avoid adding a height constraint. I'd already created a UILabel subclass that allowed me to add content insets (but for the purpose of setting tableHeaderView straight to a label without having to contain it in another view). Using the class I could set the bottom inset to solve the issue with the font clipping.

import UIKit

@IBDesignable class InsetLabel: UILabel {

    @IBInspectable var topInset: CGFloat = 16
    @IBInspectable var bottomInset: CGFloat = 16
    @IBInspectable var leftInset: CGFloat = 16
    @IBInspectable var rightInset: CGFloat = 16
    var insets: UIEdgeInsets {
        get {
            return UIEdgeInsets(
                top: topInset,
                left: leftInset,
                bottom: bottomInset,
                right: rightInset
            )
        }
    }

    override func drawText(in rect: CGRect) {
        super.drawText(in: rect.inset(by: insets))
    }

    override var intrinsicContentSize: CGSize {
        return addInsetsTo(size: super.intrinsicContentSize)
    }

    override func sizeThatFits(_ size: CGSize) -> CGSize {
        return addInsetsTo(size: super.sizeThatFits(size))
    }

    func addInsetsTo(size: CGSize) -> CGSize {
        return CGSize(
            width: size.width + leftInset + rightInset,
            height: size.height + topInset + bottomInset
        )
    }

}

This could be simplified just for the font clipping to:

import UIKit

class FontFittingLabel: UILabel {

    var inset: CGFloat = 16 // Adjust this

    override func drawText(in rect: CGRect) {
        super.drawText(in: rect.inset(by: UIEdgeInsets(
            top: 0,
            left: 0,
            bottom: inset,
            right: 0
        )))
    }

    override var intrinsicContentSize: CGSize {
        let size = super.intrinsicContentSize
        return CGSize(
            width: size.width,
            height: size.height + inset
        )
    }

}