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

17

69

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

Leduc answered 19/2, 2015 at 11:35 Comment(6)
Does the label need to be so big? You should be using the hugging and compression settings.Bullins
@Bullins UILabel height is set to min 100px and max depends on the text inside. So when the text height is less than 100px, it is displayed on vertical center to the label. I need it to be on the top left.Leduc
If you're open to it TTTAttributedLabel is an open source component that lets you set vertical alignmentAilurophobe
Possible duplicate of Vertically align text to top within a UILabelBlinnie
Hey, just give constraint to your label as top,bottom, leading, trailing. Open size inspector and set bottom constraint is equal to greater than. Make sure you have set the no.of lines to 0.Blench
@Amit Thanks a lot, pal! You save my day! This should be accepted as correct answer, as this a elegant way of doing this!Swath
A
135

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

Ailurophobe answered 24/2, 2015 at 13:55 Comment(15)
Can u help me to solve the same case when it comes in case of TTTAttributedLabel.Leduc
@Leduc TTTAttributedLabel has a verticalAlignment property that you can set to .TopAilurophobe
@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?Turin
@MKYung set numberOfLines to 0 and make sure you set the preferredMaxLayoutWidth to the width of the labels frameAilurophobe
@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.Pink
@Pink preferredMaxLayoutWidth is not a Boolean, perhaps that's your error?Ailurophobe
@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...Pink
Darn, maybe setup a github repo so we can get to the bottom of this :) @PinkAilurophobe
Thank you @DanielGalasko! Basic repo is here: github.com/zallanx/TopAlignedLabelTestPink
@Pink solved the problem and updated the SO post as well as submitted a PR on your Github. Thanks so much for the effort!Ailurophobe
@DanielGalasko Thank you for the effort and your excellent answer! Appreciate all your time.Pink
@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)];Sate
It works but I'm getting error in story board like this "Failed to render instance of TopAlignedLabel: dlopen.........................."Horsemint
Although this seems to be working, lineBreakMode with TailTrailing doesn't work. Looks like this behavior is overridden by subclassing. What's the fix?Mimimimic
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.Crankshaft
V
48

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.

Variola answered 7/12, 2015 at 9:14 Comment(3)
Yes! Best, easiest, most elegant solution here.Doriadorian
Similarly you can set height <= somethingMicamicaela
This only works if the containing view doesn't rely on the label to determine it's width and/or height.Verena
O
33

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

Ozonide answered 1/3, 2015 at 8:1 Comment(4)
this works fine for me... didn't have to override a class or anything a uitextbox did it... thanks a lot for this!Polyhymnia
Simple solution without using custom IBDesignables. Worked perfectly for me using in iOS 9.2 / Xcode 7.2.1Bulgar
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;Bulgar
Best solution so far. Works on iOS 11.2 / XCode 9.2Babarababassu
V
8

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

Valdavaldas answered 18/3, 2016 at 17:24 Comment(0)
B
7

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

Barbecue answered 4/6, 2016 at 15:37 Comment(3)
Easy solution! Worked for me tooPortage
I'm wondering why would this work, what's the science behind?Enrollment
@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 answerQuinone
B
4

You would do that by removing the minimum height.

If you need the minimum height to something else below the label then you would use a container view that resized based on the label contents but used a minimum.

Bullins answered 19/2, 2015 at 11:51 Comment(2)
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 ?Leduc
Size to fit reduces the height of the label, so it's more complicated because you have specified different requirements...Bullins
C
2

There is an easy solution for cases where the height of a label doesn't need to be constant: put your Label in a Stack View. Be sure to add leading and trailing constants to the Stack View. Here is a screenshot of how to do it in storyboard:

enter image description here

Cheiro answered 24/8, 2017 at 11:38 Comment(0)
B
1

Auto layout only work with edges/sizes of controller, not with controllers content.so its not a good idea to use auto layout to display your label text on top of first line. according to me sizetofit is a best option to do so.

Baeyer answered 24/2, 2015 at 13:16 Comment(0)
D
1

I used @Daniel Golasko's solution and whenever the text inside the UILabel was longer than the UILabel could contain, the text would start moving down instead of staying aligned to top.

I changed this line to make sure the text is aligned properly

    [super drawTextInRect:CGRectMake(0, 0, ceilf(CGRectGetWidth(self.frame)),MIN(ceilf(labelStringSize.height), self.frame.size.height))];
Deirdredeism answered 23/7, 2015 at 17:44 Comment(0)
K
1

I had a similiar issue where there were 3 labels. The middle label could have much longer text than the other two, so its height could grow much larger.

Like this:

enter image description here

I set the middle label's bottom space constraint to be >= the bottom label.

enter image description here

That solved my problem.

Kitchenware answered 12/8, 2016 at 9:40 Comment(0)
P
1

Here's an improvement on the Swift 3 solution by Daniel Galasko (here you can also set the maximum line number without an offset on the top):

import UIKit

@IBDesignable class TopAlignedLabel: UILabel {
    override func drawText(in rect: CGRect) {
        if let stringText = text {
            let stringTextAsNSString = stringText as NSString
            let labelString = stringTextAsNSString.boundingRect(with: CGSize(width: frame.width, height: .greatestFiniteMagnitude),
                    options: .usesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
            super.drawText(in: CGRect(x: 0, y: 0, width: frame.width, height: ceil(labelString.size.height) > frame.height ? frame.height : ceil(labelString.size.height)))
        } else {
            super.drawText(in: rect)
        }
    }

    override func prepareForInterfaceBuilder() {
        super.prepareForInterfaceBuilder()
        layer.borderWidth = 1
        layer.borderColor = UIColor.black.cgColor
    }
}
Portraiture answered 22/7, 2017 at 12:0 Comment(0)
F
1

Swift 4

You should subclass UILabel and override text display rendering.

class UITopAlignedLabel: UILabel {
    override func drawText(in rect: CGRect) {
        guard let string = text else {
            super.drawText(in: rect)
            return
        }

        let size = (string as NSString).boundingRect(
            with: CGSize(width: rect.width, height: .greatestFiniteMagnitude),
            options: [.usesLineFragmentOrigin],
            attributes: [.font: font],
            context: nil).size

        var rect = rect
        rect.size.height = size.height.rounded()
        super.drawText(in: rect)
    }
}
Farcical answered 30/11, 2018 at 11:24 Comment(0)
C
0

You can try if button [button setContentVerticalAlignment:UIControlContentVerticalAlignmentTop];

Edit :

You can try with this if you want to use label only:

https://mcmap.net/q/281528/-vertically-align-uilabel

Croak answered 19/2, 2015 at 12:43 Comment(1)
I didnt get the option [label setContentVerticalAlignmen...] for UILabel. It is for Button only right ?Leduc
F
0

For me, I didn't set the height constraint, the text always grows from the top of the label. The constraints for this label are top, left, right. By the way, my label has fixed line numbers, so no worries about the height.

Floorboard answered 28/10, 2016 at 21:16 Comment(0)
H
0
  @IBInspectable var alignTop: Bool = false

  func setAlignTop() {
    let text = self.text!
    let lines = text.characters.split(separator: "\n").count
    if lines < self.numberOfLines {
      var newLines = ""
      for _ in 0..<(self.numberOfLines - lines) {
        newLines = newLines.appending("\n ")
      }
      self.text! = text.appending(newLines)
    }
  }

  override var text: String? {
    didSet {
      if alignTop {
        self.setAlignTop()
      }
    }
  }
Housebroken answered 21/12, 2016 at 3:1 Comment(0)
S
0

use this my class, you can change text alignment by contentMode.

supported case: .top, .bottom, .left, .right, .topLeft, .topRight, .bottomLeft, .bottomRight

Swift4

import Foundation
import UIKit

@IBDesignable
class UIAlignedLabel: UILabel {

    override func drawText(in rect: CGRect) {
        if let text = text as NSString? {
            func defaultRect(for maxSize: CGSize) -> CGRect {
                let size = text
                    .boundingRect(
                        with: maxSize,
                        options: NSStringDrawingOptions.usesLineFragmentOrigin,
                        attributes: [
                            NSAttributedStringKey.font: font
                        ],
                        context: nil
                    ).size
                let rect = CGRect(
                    origin: .zero,
                    size: CGSize(
                        width: min(frame.width, ceil(size.width)),
                        height: min(frame.height, ceil(size.height))
                    )
                )
                return rect

            }
            switch contentMode {
            case .top, .bottom, .left, .right, .topLeft, .topRight, .bottomLeft, .bottomRight:
                let maxSize = CGSize(width: frame.width, height: frame.height)
                var rect = defaultRect(for: maxSize)
                switch contentMode {
                    case .bottom, .bottomLeft, .bottomRight:
                        rect.origin.y = frame.height - rect.height
                    default: break
                }
                switch contentMode {
                    case .right, .topRight, .bottomRight:
                        rect.origin.x = frame.width - rect.width
                    default: break
                }
                super.drawText(in: rect)
            default:
                super.drawText(in: rect)
            }
        } else {
            super.drawText(in: rect)
        }
    }

}
Sibylsibylla answered 26/11, 2018 at 12:57 Comment(0)
M
0

In the Interface Builder, just make the height <= some value instead of =. This will enable to text to start at the top and expand the height as needed. For example, I have a label with a height proportional to the size of the main view. So my height constraint looks like this: Height Constraint

Metathesis answered 25/11, 2019 at 15:30 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.