How do I sizeToFit a UILabel by changing only the height and not the width?
Asked Answered
S

10

39
[self.Review sizeToFit];

Result before sizeToFit:

NSStringFromCGRect(self.Review.frame): {{90, 20}, {198, 63}}

Result After sizeToFit:

NSStringFromCGRect(self.Review.frame): {{90, 20}, {181, 45}}

I want the width to remain the same. I just want to change the height. THe automask is

(lldb) po self.Review
(UILabel *) $1 = 0x08bb0fe0 <UILabel: 0x8bb0fe0; frame = (90 20; 181 45); text = 'I'm at Mal Taman Anggrek ...'; clipsToBounds = YES; opaque = NO; autoresize = LM+RM+H; userInteractionEnabled = NO; layer = <CALayer: 0x8bb68b0>>

I know that there is a way to do so with: How to adjust and make the width of a UILabel to fit the text size?

The answers are either strange (we need to resupply the font information). Or unclear.

You will also need to define a maximum width, and tell your program what to do if sizeToFit gives you a width greater than that maximum.

I will use the strange solution of using sizeWithFont. It's strange because UILabel already knows the font in the label.

Actually how does sizeToFit behave anyway? How does it decide whether we need thinner or taller UILabel?

Slipon answered 25/10, 2012 at 11:44 Comment(1)
sizeWithFont is a method of NSString not of the UILableYevetteyew
W
41

This is how it is done. Because the label already contains the font information, including it in this method call is trivial.

CGSize size = [label.text sizeWithFont:label.font
                     constrainedToSize:CGSizeMake(maxWidth, MAXFLOAT)
                         lineBreakMode:UILineBreakModeWordWrap]; 
CGRect labelFrame = label.frame;
labelFrame.size.height = size.height;
label.frame = labelFrame;

Swift version using the more up-to-date boundingRectWithSize:

let maxHeight = CGFloat.infinity
let rect = label.attributedText?.boundingRectWithSize(CGSizeMake(maxWidth, maxHeight), 
       options: .UsesLineFragmentOrigin, context: nil)
var frame = label.frame
frame.size.height = rect.size.height
label.frame = frame
Wampumpeag answered 25/10, 2012 at 12:23 Comment(8)
and maxWidth will just be the label.frame.size.width?Slipon
Right. I thought that you might want to specify that somewhere else, too. But if the label already has the right size, label.frame.size.width will leave it as is.Wampumpeag
I originally used sizeToFit, but found that sometimes when the text would have fit in one line, it split it into two for no apparent reason. This approach does not cause this issueLomalomas
sizetofit sometime it's not update, but this one actually work!Hungerford
sizeWithFont has been deprecated for a while now. See UILabel's sizeThatFits and NSString's boundingRectWithSizeCavein
@Cavein If you look carefully, the Swift version does use boundingRectWithSize.Wampumpeag
You should not use sizeWithFont etc. Mike Weller puts it better than I could: doing-it-wrong.mikeweller.com/2012/07/…Delinda
Among other problems, you need to use CGFloat.greatestFiniteMagnitude, not like "1000".Mowery
H
44

You can achieve the same result with sizeThatFits.

CGSize size = [label sizeThatFits:CGSizeMake(label.frame.size.width, CGFLOAT_MAX)];
CGRect frame = label.frame;
frame.size.height = size.height;
label.frame = frame;

Or alternatively, with sizeToFit.

CGRect frame = label.frame;
[label sizeToFit];
frame.size.height = label.frame.size.height;
label.frame = frame;
Hairtail answered 23/1, 2013 at 11:11 Comment(2)
Ended using your solution! But just one correction... He wanted to keep the HEIGTH and on your second option (the one I used) you are keeping the WIDTH.Fosque
When using iOS 7 (or 8), this is the best working answer since sizeWithFont: is deprecated for iOS 7 and greaterFumy
W
41

This is how it is done. Because the label already contains the font information, including it in this method call is trivial.

CGSize size = [label.text sizeWithFont:label.font
                     constrainedToSize:CGSizeMake(maxWidth, MAXFLOAT)
                         lineBreakMode:UILineBreakModeWordWrap]; 
CGRect labelFrame = label.frame;
labelFrame.size.height = size.height;
label.frame = labelFrame;

Swift version using the more up-to-date boundingRectWithSize:

let maxHeight = CGFloat.infinity
let rect = label.attributedText?.boundingRectWithSize(CGSizeMake(maxWidth, maxHeight), 
       options: .UsesLineFragmentOrigin, context: nil)
var frame = label.frame
frame.size.height = rect.size.height
label.frame = frame
Wampumpeag answered 25/10, 2012 at 12:23 Comment(8)
and maxWidth will just be the label.frame.size.width?Slipon
Right. I thought that you might want to specify that somewhere else, too. But if the label already has the right size, label.frame.size.width will leave it as is.Wampumpeag
I originally used sizeToFit, but found that sometimes when the text would have fit in one line, it split it into two for no apparent reason. This approach does not cause this issueLomalomas
sizetofit sometime it's not update, but this one actually work!Hungerford
sizeWithFont has been deprecated for a while now. See UILabel's sizeThatFits and NSString's boundingRectWithSizeCavein
@Cavein If you look carefully, the Swift version does use boundingRectWithSize.Wampumpeag
You should not use sizeWithFont etc. Mike Weller puts it better than I could: doing-it-wrong.mikeweller.com/2012/07/…Delinda
Among other problems, you need to use CGFloat.greatestFiniteMagnitude, not like "1000".Mowery
C
17

sizeToFit works great. The only problem is that it is based on the current size of the control.

For instance if you are recycling table view cells with a label, the label may have been reduced in width in a previous cell and so, the call to sizeToFit may look unreliable.

Just reset the original width of your control before you send the sizeToFit message.

Credential answered 12/12, 2013 at 13:52 Comment(5)
this is priceless info that was under appreciatedBlowing
@ChuckKelly, I totally agree.Juvenilia
Thanks so much, that makes everything so much easier!Haruspicy
exactly my problem. but how to reset to the original width? Its set by a leading/trailing constraints.Norven
I that case you shoul not have to size your control. Or the problem is with height?Credential
D
5

An easy extension for this problem for Swift 3.

extension UILabel {
    func sizeToFitHeight() {
        let size: CGSize = self.sizeThatFits(CGSize.init(width: self.frame.size.width, height: CGFloat.greatestFiniteMagnitude))
        var frame:CGRect = self.frame
        frame.size.height = size.height
        self.frame = frame
    }
}
Deodorize answered 9/3, 2017 at 4:55 Comment(0)
D
2

I think you should not use NSString's size... methods, UILabel already has an API for just that, as @vatrif points out (which internally probably uses just NSString's methods).

Nevertheless I think UILabel's API could be improved. Thats why I think a category would be the most elegant solution:

//  UILabel+Resize.h
@interface UILabel (Resize)
- (void)sizeToFitHeight;
@end

//  UILabel+Resize.m
@implementation UILabel (Resize)
- (void)sizeToFitHeight {
    CGSize size = [self sizeThatFits:CGSizeMake(self.frame.size.width, CGFLOAT_MAX)];
    CGRect frame = self.frame;
    frame.size.height = size.height;
    self.frame = frame;
}
@end

Just one thing: I am not sure about the appropriate name for that method:

  • sizeToFitHeight?
  • heightToFit ?

Comments very much appreciated, thanks!

Delinda answered 15/5, 2014 at 23:35 Comment(0)
S
2

Agreed API would benefit from this addition. Make your life easier and add an extension to the UILabel class in Swift as follows:

extension UILabel {

    func sizeToFitHeight() {
        let size:CGSize = self.sizeThatFits(CGSizeMake(self.frame.size.width, CGFloat.max))
        var frame:CGRect = self.frame
        frame.size.height = size.height
        self.frame = frame
    }
}
Scatology answered 16/3, 2016 at 15:54 Comment(0)
M
2

Answer for 2017...

c = CGSize(width: yourWidth, height: CGFloat.greatestFiniteMagnitude)
result = sizeThatFits(c).height

So...

let t = .... your UITextView
let w = .... your known width of the item

let trueHeight = t.sizeThatFits(
                   CGSize(width: t, height: CGFloat.greatestFiniteMagnitude)).height

That's it.


Very often, you want to know the difference in height compared to a "normal" one-line entry.

This comes up in particular if you are making some sort of cells that have nothing to do with UTableView (say, some sort of custom stack view). You have to set the height manually at some point. On your storyboard, build it as you wish, using a sample text of any short string (eg, "A") which of course will have only one line in any normal situation. Then you do something like this...

func setTextWithSizeCorrection() { // an affaire iOS

    let t = .... your UITextView
    let w = .... your known width of the item

    let oneLineExample = "A"
    let actualText = yourDataSource[row].description.whatever
    
    // get the height as if with only one line...
    
    experimental.text = oneLineExample
    let oneLineHeight = t.sizeThatFits(
               CGSize(width: w, height: CGFloat.greatestFiniteMagnitude)).height
    
    // get the height with the actual long text...
    // (note that usually, you do not want a one-line minimum)
    
    experimental.text = (actualText == "") ? oneLineExample : actualText
    let neededHeight = t.sizeThatFits(
              CGSize(width: w, height: CGFloat.greatestFiniteMagnitude)).height
    
    experimental.text = actualText
    
    let delta = neededHeight - oneLineHeight
    
    set the height of your cell, or whatever it is(standardHeight + delta)
}

Final point: one of the really stupid things in iOS is that UITextView has a bizarre sort of margin inside it. This means you can't just swap between UITextViews and labels; and it causes many other problems. Apple haven't fixed this issue as of writing. To fix the problem is difficult: the following approach is usually OK:

@IBDesignable class UITextViewFixed: UITextView {
    override func layoutSubviews() {
        super.layoutSubviews()
        setup()
    }
    func setup() {
        textContainerInset = UIEdgeInsets.zero;
        textContainer.lineFragmentPadding = 0;
    }
}

Broken, unusable UITextView from Apple...

enter image description here

UITextViewFixed which is, in most cases, an acceptable fix:

enter image description here

(Yellow added for clarity.)

Mowery answered 19/2, 2017 at 22:7 Comment(0)
H
1

FIXED SOLUTION FOR SWIFT 3

Use CGFloat.greatestFiniteMagnitude for maxHeight.

let size = CGSize.init(width: yourLabel.frame.size.width,
                       height: CGFloat.greatestFiniteMagnitude)
let rect = errorLabel.attributedText?.boundingRect(with: size, options: .usesLineFragmentOrigin, context: nil)
var frame = errorLabel.frame
frame.size.height = (rect?.size.height)!
errorLabel.frame = frame


OR Create UIlabel extension, and call method yourLabel. sizeToFitHeight()

extension UILabel {

    func sizeToFitHeight() {
        let maxHeight : CGFloat = CGFloat.greatestFiniteMagnitude
        let size = CGSize.init(width: self.frame.size.width, height: maxHeight)
        let rect = self.attributedText?.boundingRect(with: size, options: .usesLineFragmentOrigin, context: nil)
        var frame = self.frame
        frame.size.height = (rect?.size.height)!
        self.frame = frame
    }

} 
Highlands answered 11/2, 2017 at 13:36 Comment(0)
G
1

I believe I have a much simpler solution:

let oldWidth = self.frame.size.width
label.sizeToFit()
label.frame.size.width = oldWidth

Simply store what the old width was, sizeToFit() applies to both width and height, then reset the width back to it's old value leaving only the label's height changed.

Garrulity answered 17/10, 2019 at 0:2 Comment(0)
P
0

Swift 3 version of Mundi's answer:

let maxHeight: CGFloat = 10000
let rect = label.attributedText!.boundingRect(with: CGSize(width: maxWidth, height: maxHeight),
                                                           options: .usesLineFragmentOrigin,
                                                           context: nil)
var frame = labe.frame
frame.size.height = rect.size.height
label.frame = frame
Penelope answered 16/12, 2016 at 2:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.