Thursday, May 24, 2012

How do I vertically align text within a UILabel?


I have an UILabel with two lines. Sometimes, when the text is short enough, this text is displayed in the vertical center of the UILabel.



How do I vertically align my text at the top of the UILabel?



alt text


Source: Tips4all

9 comments:

  1. There's no way to set the vertical align on a UILabel, but you can get the same effect by changing the label's frame. I've made my labels orange so you can see clearly what's happening.

    Here's the quick and easy way to do this:

    [myLabel sizeToFit];






    If you have a label with longer text that will make more than one line, set numberOfLines to 0 (zero here means an unlimited number of lines).

    myLabel.numberOfLines = 0;
    [myLabel sizeToFit];






    Longer Version

    I'll make my label in code so that you can see what's going on. You can set up most of this in Interface Builder too. My setup is a View Based App with a background image I made in Photoshop to show margins (20 points). The label is an attractive orange color so you can see what's going on with the dimensions.

    - (void)viewDidLoad
    {
    [super viewDidLoad];

    // 20 point top and left margin. Sized to leave 20 pt at right.
    CGRect labelFrame = CGRectMake(20, 20, 280, 150);
    UILabel *myLabel = [[UILabel alloc] initWithFrame:labelFrame];
    [myLabel setBackgroundColor:[UIColor orangeColor]];

    NSString *labelText = @"I am the very model of a modern Major-General, I've information vegetable, animal, and mineral";
    [myLabel setText:labelText];

    // Tell the label to use an unlimited number of lines
    [myLabel setNumberOfLines:0];
    [myLabel sizeToFit];

    [self.view addSubview:myLabel];
    }


    Some limitations of using sizeToFit come into play with center- or right-aligned text. Here's what happens:

    // myLabel.textAlignment = UITextAlignmentRight;
    myLabel.textAlignment = UITextAlignmentCenter;

    [myLabel setNumberOfLines:0];
    [myLabel sizeToFit];




    The label is still sized with a fixed top-left corner. You can save the original label's width in a variable and set it after sizeToFit, or give it a fixed width to counter these problems:

    myLabel.textAlignment = UITextAlignmentCenter;

    [myLabel setNumberOfLines:0];
    [myLabel sizeToFit];

    CGRect myFrame = myLabel.frame;
    // Resize the frame's width to 280 (320 - margins)
    // width could also be myOriginalLabelFrame.size.width
    myFrame = CGRectMake(myFrame.origin.x, myFrame.origin.y, 280, myFrame.size.height);
    myLabel.frame = myFrame;






    Note that sizeToFit will respect your initial label's minimum width. If you start with a label 100 wide and call sizeToFit on it, it will give you back a (possibly very tall) label with 100 (or a little less) width. You might want to set your label to the minimum width you want before resizing.



    Some other things to note:

    Whether lineBreakMode is respected depends on how it's set. UILineBreakModeTailTruncation (the default) is ignored after sizeToFit, as are the other two truncation modes (head and middle). UILineBreakModeClip is also ignored. UILineBreakModeCharacterWrap works as usual. The frame width is still narrowed to fit to the rightmost letter.

    My Original Answer (for posterity/reference):

    This uses the NSString method sizeWithFont:constrainedToSize:lineBreakMode: to calculate the frame height needed to fit a string, then sets the origin and width.

    Resize the frame for the label using the text you want to insert. That way you can accommodate any number of lines.

    CGSize maximumSize = CGSizeMake(300, 9999);
    NSString *dateString = @"The date today is January 1st, 1999";
    UIFont *dateFont = [UIFont fontWithName:@"Helvetica" size:14];
    CGSize dateStringSize = [dateString sizeWithFont:dateFont
    constrainedToSize:maximumSize
    lineBreakMode:self.dateLabel.lineBreakMode];

    CGRect dateFrame = CGRectMake(10, 10, 300, dateStringSize.height);

    self.dateLabel.frame = dateFrame;


    This page has some different code for the same solution:

    http://discussions.apple.com/thread.jspa?threadID=1759957

    ReplyDelete
  2. 1) Set the new text:

    myLabel.text = @"Some Text"

    2) Set the maximum number of lines to 0 (automatic):

    myLabel.numberOfLines = 0

    3) Set the frame of the label to the maximum size:

    myLabel.frame = CGRectMake(20,20,200,800)

    4) Call sizeToFit to reduce the frame size so the contents just fit:

    [myLabel sizeToFit]

    The labels frame is now just high and wide enough to fit your text. The top left should be unchanged. I have tested this only with top left aligned text. For other alignments, you might have to modify the frame afterwards.

    Also, my label has word wrapping enabled.

    ReplyDelete
  3. Refering to the extension solution:

    for(int i=1; i< newLinesToPad; i++)
    self.text = [self.text stringByAppendingString:@"\n"];


    should be replaced by

    for(int i=0; i<newLinesToPad; i++)
    self.text = [self.text stringByAppendingString:@"\n "];


    Additional space is needed in every added newline, because iPhone UILabels' trailing carriage returns seems to be ignored :(

    Similarly, alignBottom should be updated too with a @" \n@%" in place of "\n@%" (for cycle initialization must be replaced by "for(int i=0..." too).

    The following extension works for me:

    // -- file: UILabel+VerticalAlign.h
    #pragma mark VerticalAlign
    @interface UILabel (VerticalAlign)
    - (void)alignTop;
    - (void)alignBottom;
    @end

    // -- file: UILabel+VerticalAlign.m
    @implementation UILabel (VerticalAlign)
    - (void)alignTop {
    CGSize fontSize = [self.text sizeWithFont:self.font];
    double finalHeight = fontSize.height * self.numberOfLines;
    double finalWidth = self.frame.size.width; //expected width of label
    CGSize theStringSize = [self.text sizeWithFont:self.font constrainedToSize:CGSizeMake(finalWidth, finalHeight) lineBreakMode:self.lineBreakMode];
    int newLinesToPad = (finalHeight - theStringSize.height) / fontSize.height;
    for(int i=0; i<newLinesToPad; i++)
    self.text = [self.text stringByAppendingString:@"\n "];
    }

    - (void)alignBottom {
    CGSize fontSize = [self.text sizeWithFont:self.font];
    double finalHeight = fontSize.height * self.numberOfLines;
    double finalWidth = self.frame.size.width; //expected width of label
    CGSize theStringSize = [self.text sizeWithFont:self.font constrainedToSize:CGSizeMake(finalWidth, finalHeight) lineBreakMode:self.lineBreakMode];
    int newLinesToPad = (finalHeight - theStringSize.height) / fontSize.height;
    for(int i=0; i<newLinesToPad; i++)
    self.text = [NSString stringWithFormat:@" \n%@",self.text];
    }
    @end


    Then call [yourLabel alignTop]; or [yourLabel alignBottom]; after each yourLabel text assignment.

    ReplyDelete
  4. Like the answer above, but it wasn't quite right, or easy to slap into code so I cleaned it up a bit. Add this extension either to it's own .h and .m file or just paste right above the implementation you intend to use it:

    #pragma mark VerticalAlign
    @interface UILabel (VerticalAlign)
    - (void)alignTop;
    - (void)alignBottom;
    @end


    @implementation UILabel (VerticalAlign)
    - (void)alignTop
    {
    CGSize fontSize = [self.text sizeWithFont:self.font];

    double finalHeight = fontSize.height * self.numberOfLines;
    double finalWidth = self.frame.size.width; //expected width of label


    CGSize theStringSize = [self.text sizeWithFont:self.font constrainedToSize:CGSizeMake(finalWidth, finalHeight) lineBreakMode:self.lineBreakMode];


    int newLinesToPad = (finalHeight - theStringSize.height) / fontSize.height;

    for(int i=0; i<= newLinesToPad; i++)
    {
    self.text = [self.text stringByAppendingString:@" \n"];
    }
    }

    - (void)alignBottom
    {
    CGSize fontSize = [self.text sizeWithFont:self.font];

    double finalHeight = fontSize.height * self.numberOfLines;
    double finalWidth = self.frame.size.width; //expected width of label


    CGSize theStringSize = [self.text sizeWithFont:self.font constrainedToSize:CGSizeMake(finalWidth, finalHeight) lineBreakMode:self.lineBreakMode];


    int newLinesToPad = (finalHeight - theStringSize.height) / fontSize.height;

    for(int i=0; i< newLinesToPad; i++)
    {
    self.text = [NSString stringWithFormat:@" \n%@",self.text];
    }
    }
    @end


    And then to use, put your text into the label, and then call the appropriate method to align it:

    [myLabel alignTop];


    or

    [myLabel alignBottom];

    ReplyDelete
  5. An even quicker (and dirtier) way to accomplish this is by setting the UILabel's line break mode to "Clip" and adding a fixed amount of newlines.

    myLabel.lineBreakMode = UILineBreakModeClip;
    myLabel.text = [displayString stringByAppendingString:"\n\n\n\n"];


    This solution won't work for everyone -- in particular, if you still want to show "..." at the end of your string if it exceeds the number of lines you're showing, you'll need to use one of the longer bits of code -- but for a lot of cases this'll get you what you need.

    ReplyDelete
  6. Create a new class

    LabelTopAlign

    .h file

    #import <UIKit/UIKit.h>


    @interface KwLabelTopAlign : UILabel {

    }

    @end


    .m file

    #import "KwLabelTopAlign.h"


    @implementation KwLabelTopAlign

    - (void)drawTextInRect:(CGRect)rect {
    int lineHeight = [@"IglL" sizeWithFont:self.font constrainedToSize:CGSizeMake(rect.size.width, 9999.0f)].height;
    if(rect.size.height >= lineHeight) {
    int textHeight = [self.text sizeWithFont:self.font constrainedToSize:CGSizeMake(rect.size.width, rect.size.height)].height;
    int yMax = textHeight;
    if (self.numberOfLines > 0) {
    yMax = MIN(lineHeight*self.numberOfLines, yMax);
    }

    [super drawTextInRect:CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, yMax)];
    }
    }

    @end

    ReplyDelete
  7. In Interface Builder


    Set UILabel to size of biggest possible Text
    Set 'Lines' to '0' in Attributes Inspector


    In your code


    Set the text of the label
    Call sizeToFit on your label


    Code Snippet:

    self.myLabel.text = @"Short Title";
    [self.myLabel sizeToFit];

    ReplyDelete
  8. I took a while to read the code, as well as the code in the introduced page, and found that they all try to modify the frame size of label, so that the default center vertical alignment would not appear.

    however, in some cases we do want the label to occupy all those spaces, even if the label does have so much text (e.g. multiple rows with equal height)

    here, I used an alternative way to solve it, by simply pad newlines to the end of label (pls note that I actually inherited the UILabel, but it is not necessary):

    CGSize fontSize = [self.text sizeWithFont:self.font];

    finalHeight = fontSize.height * self.numberOfLines;
    finalWidth = size.width; //expected width of label


    CGSize theStringSize = [self.text sizeWithFont:self.font constrainedToSize:CGSizeMake(finalWidth, finalHeight) lineBreakMode:self.lineBreakMode];


    int newLinesToPad = (finalHeight - theStringSize.height) / fontSize.height;

    for(int i=0; i< newLinesToPad; i++)
    {
    self.text = [self.text stringByAppendingString:@"\n "];
    }

    ReplyDelete
  9. I wrote a util function to achieve this purpose. You can take a look:


    // adjust the height of a multi-line label to make it align vertical with top
    + (void) alignLabelWithTop:(UILabel *)label {
    CGSize maxSize = CGSizeMake(label.frame.size.width, 999);
    label.adjustsFontSizeToFitWidth = NO;

    // get actual height
    CGSize actualSize = [label.text sizeWithFont:label.font constrainedToSize:maxSize lineBreakMode:label.lineBreakMode];
    CGRect rect = label.frame;
    rect.size.height = actualSize.height;
    label.frame = rect;
    }


    .How to use? (If lblHello is created by Interface builder, so I skip some UILabel attributes detail)


    lblHello.text = @"Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World! Hello World!";
    lblHello.numberOfLines = 5;
    [Utils alignLabelWithTop:lblHello];


    I also wrote it on my blog as an article:
    http://fstoke.me/blog/?p=2819

    ReplyDelete