Vertically align text within a UILabel (Note : Using AutoLayout)

78,806

Solution 1

Edit

In my original answer I was using the paragraph style of the label. Turns out that for multi-line labels this actually prevents the label from being multi-line. As a result I removed it from the calculation. See more about this in Github

For those of you more comfortable with using Open Source definitely look at TTTAttributedLabel where you can set the label's text alignment to TTTAttributedLabelVerticalAlignmentTop


The trick is to subclass UILabel and override drawTextInRect. Then enforce that the text is drawn at the origin of the label's bounds.

Here's a naive implementation that you can use right now:

Swift

@IBDesignable class TopAlignedLabel: UILabel {
    override func drawTextInRect(rect: CGRect) {
        if let stringText = text {
            let stringTextAsNSString = stringText as NSString
            var labelStringSize = stringTextAsNSString.boundingRectWithSize(CGSizeMake(CGRectGetWidth(self.frame), CGFloat.max),
                options: NSStringDrawingOptions.UsesLineFragmentOrigin,
                attributes: [NSFontAttributeName: font],
                context: nil).size
            super.drawTextInRect(CGRectMake(0, 0, CGRectGetWidth(self.frame), ceil(labelStringSize.height)))
        } else {
            super.drawTextInRect(rect)
        }
    }
    override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        layer.borderWidth = 1
        layer.borderColor = UIColor.blackColor().CGColor
    }
}

Swift 3

  @IBDesignable class TopAlignedLabel: UILabel {
    override func drawText(in rect: CGRect) {
        if let stringText = text {
            let stringTextAsNSString = stringText as NSString
            let labelStringSize = stringTextAsNSString.boundingRect(with: CGSize(width: self.frame.width,height: CGFloat.greatestFiniteMagnitude),
                                                                            options: NSStringDrawingOptions.usesLineFragmentOrigin,
                                                                            attributes: [NSFontAttributeName: font],
                                                                            context: nil).size
            super.drawText(in: CGRect(x:0,y: 0,width: self.frame.width, height:ceil(labelStringSize.height)))
        } else {
            super.drawText(in: rect)
        }
    }
    override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        layer.borderWidth = 1
        layer.borderColor = UIColor.black.cgColor
    }
}

Objective-C

IB_DESIGNABLE
@interface TopAlignedLabel : UILabel

@end

@implementation TopAlignedLabel

- (void)drawTextInRect:(CGRect)rect {
    if (self.text) {
        CGSize labelStringSize = [self.text boundingRectWithSize:CGSizeMake(CGRectGetWidth(self.frame), CGFLOAT_MAX)
                                                         options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
                                                      attributes:@{NSFontAttributeName:self.font}
                                                         context:nil].size;
        [super drawTextInRect:CGRectMake(0, 0, ceilf(CGRectGetWidth(self.frame)),ceilf(labelStringSize.height))];
    } else {
        [super drawTextInRect:rect];
    }
}

- (void)prepareForInterfaceBuilder {
        [super prepareForInterfaceBuilder];
        self.layer.borderWidth = 1;
        self.layer.borderColor = [UIColor blackColor].CGColor;
}

@end

Since I used IBDesignable you can add this label to a storyboard and watch it go, this is what it looks like for me

enter image description here

Solution 2

If you're not restricted by having UILabel of fixed size, instead of aligning the text within a UILabel, simply use ≥ constraint on the given label to change the size of it.

enter image description here

It's the most elegant solution using Auto Layout. Don't forget to set numberOfLines to zero though.

Solution 3

You can use UITextView instead of UILabel:
Uncheck "Scrolling enabled"
Uncheck "Editable"
Uncheck "Selectable"
Set background color to ClearColor

Solution 4

I had the same problem, and this is how I solved it. I just edited the Baseline under Attribute Inspector for the Label. Set it to "Align Centers".

Setting baseline to Align Centers

Solution 5

Instead, I changed the Bottom Space Constant to priority @250 and solved my problem. And my label has height constant with <= constant

Share:
78,806
MELWIN
Author by

MELWIN

Updated on November 26, 2020

Comments

  • MELWIN
    MELWIN over 3 years

    I am Copying the same Question asked Before Question. I have tried the solutions given and was not able to solve it since sizetofit was not effective when I use Autolayout.

    first screenshot

    The expected display is like below.

    second screenshot

  • MELWIN
    MELWIN about 9 years
    Yes, the label height is connected to another view's top placed below the label. I understood your solution, but that makes my tablecellview more complicated, it already have a lot of constrains connecting each other. Will you please suggest me another solution like sizetofit ?
  • Wain
    Wain about 9 years
    Size to fit reduces the height of the label, so it's more complicated because you have specified different requirements...
  • MELWIN
    MELWIN about 9 years
    I didnt get the option [label setContentVerticalAlignmen...] for UILabel. It is for Button only right ?
  • MELWIN
    MELWIN almost 9 years
    Can u help me to solve the same case when it comes in case of TTTAttributedLabel.
  • Daniel Galasko
    Daniel Galasko almost 9 years
    @MELWIN TTTAttributedLabel has a verticalAlignment property that you can set to .Top
  • MELWIN
    MELWIN almost 9 years
    @daneil Thank u. Worked fine
  • MK Yung
    MK Yung almost 9 years
    @DanielGalasko if the text is longer than the width of the label it turns into ellipsis rather than going next line. I have already set Lines: 0 in the XCode but it does not work. How to fix it?
  • Daniel Galasko
    Daniel Galasko almost 9 years
    @MKYung set numberOfLines to 0 and make sure you set the preferredMaxLayoutWidth to the width of the labels frame
  • daspianist
    daspianist almost 9 years
    @DanielGalasko Thank you for the solution. I seem to have the same problem as MK (I also ensured that lines were set to 0, and preferredMaxLayoutWidth = YES. When testing using static text that's 2 lines, this solution works. However, when I have a long text, the "..." ellipses show up. Thanks for looking into it.
  • Daniel Galasko
    Daniel Galasko almost 9 years
    @daspianist preferredMaxLayoutWidth is not a Boolean, perhaps that's your error?
  • daspianist
    daspianist almost 9 years
    @DanielGalasko ah you're right, and thanks for your reply! I am using Storyboard (the UILabel appear inside a UITableViewCell), and changed the max width to the same width that the label appears inside my cell. However, the ellipses persist...
  • Daniel Galasko
    Daniel Galasko almost 9 years
    Darn, maybe setup a github repo so we can get to the bottom of this :) @daspianist
  • daspianist
    daspianist almost 9 years
    Thank you @DanielGalasko! Basic repo is here: github.com/zallanx/TopAlignedLabelTest
  • Daniel Galasko
    Daniel Galasko almost 9 years
    @daspianist solved the problem and updated the SO post as well as submitted a PR on your Github. Thanks so much for the effort!
  • daspianist
    daspianist almost 9 years
    @DanielGalasko Thank you for the effort and your excellent answer! Appreciate all your time.
  • MizAkita
    MizAkita about 8 years
    this works fine for me... didn't have to override a class or anything a uitextbox did it... thanks a lot for this!
  • Mark Barrasso
    Mark Barrasso about 8 years
    Simple solution without using custom IBDesignables. Worked perfectly for me using in iOS 9.2 / Xcode 7.2.1
  • Mark Barrasso
    Mark Barrasso about 8 years
    Also, in order for the text within the UITextView to align perfectly with another UILabel, I used this line of code: self.textView.textContainer.lineFragmentPadding = 0;
  • CharlesA
    CharlesA about 8 years
    @DanielGalasko I found that using this code with text that was too big to fit the label's frame led to an offset appearing at the top. Fixed with CGFloat height = MIN(labelStringSize.height, CGRectGetHeight(self.frame)); [super drawTextInRect:CGRectMake(0, 0, ceilf(CGRectGetWidth(self.frame)), height)];
  • siva krishna
    siva krishna over 7 years
    It works but I'm getting error in story board like this "Failed to render instance of TopAlignedLabel: dlopen.........................."
  • elquimista
    elquimista over 7 years
    Although this seems to be working, lineBreakMode with TailTrailing doesn't work. Looks like this behavior is overridden by subclassing. What's the fix?
  • Zack
    Zack over 7 years
    Yes! Best, easiest, most elegant solution here.
  • Dale
    Dale about 7 years
    Similarly you can set height <= something
  • Glenn Sønderskov
    Glenn Sønderskov almost 7 years
    Easy solution! Worked for me too
  • Ruiz
    Ruiz about 6 years
    This only works if the containing view doesn't rely on the label to determine it's width and/or height.
  • Darkwonder
    Darkwonder about 6 years
    Best solution so far. Works on iOS 11.2 / XCode 9.2
  • Luis Pena
    Luis Pena almost 6 years
    I'm wondering why would this work, what's the science behind?
  • David Pettigrew
    David Pettigrew over 5 years
    As others have stated, it doesn't work if the text is truncated. e.g. Using numberOfLines = 2 with lineBreakMode = .byTruncatingTail and the text doesn't fit. There is extra padding above the text.
  • NSGangster
    NSGangster over 4 years
    @LuisPena By default Vertical Content Hugging priority on views set in storyboard is 251, when layout engine sees bottom constraint only has 250 priority but priority to hug content is higher, it chooses to hug content. By default constraints are set to 1000, which is why it only works by setting priority lower for engine to "break" constraint, I prefer having the >= constraint as in Nikolai's answer