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?

Community
  • 1
  • 1
Anonymous White
  • 2,149
  • 3
  • 20
  • 27

10 Answers10

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;
Snowman
  • 31,411
  • 46
  • 180
  • 303
vadimtrifonov
  • 818
  • 8
  • 7
  • 1
    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. – Thpramos Apr 02 '14 at 21:07
  • When using iOS 7 (or 8), this is the best working answer since sizeWithFont: is deprecated for iOS 7 and greater – BrFreek Jul 29 '15 at 14:44
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
Mundi
  • 79,884
  • 17
  • 117
  • 140
  • and maxWidth will just be the label.frame.size.width? – Anonymous White Oct 25 '12 at 19:29
  • 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. – Mundi Oct 25 '12 at 21:26
  • 1
    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 issue – bengoesboom Aug 08 '13 at 17:34
  • sizetofit sometime it's not update, but this one actually work! – Sruit A.Suk Mar 30 '15 at 15:06
  • 1
    `sizeWithFont` has been deprecated for a while now. See UILabel's `sizeThatFits` and NSString's `boundingRectWithSize` – Yerk Jun 14 '15 at 23:27
  • @Yerk If you look carefully, the Swift version *does* use `boundingRectWithSize`. – Mundi Jun 16 '15 at 08:23
  • You should not use `sizeWithFont` etc. Mike Weller puts it better than I could: http://doing-it-wrong.mikeweller.com/2012/07/youre-doing-it-wrong-2-sizing-labels.html – de. Feb 06 '16 at 22:03
  • Among other problems, you need to use `CGFloat.greatestFiniteMagnitude`, not like "1000". – Fattie Feb 19 '17 at 21:54
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.

chrilith
  • 260
  • 3
  • 8
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
    }
}
ndduong
  • 429
  • 9
  • 21
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!

de.
  • 7,068
  • 3
  • 40
  • 69
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
    }
}
dijipiji
  • 3,063
  • 1
  • 26
  • 21
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.)

Community
  • 1
  • 1
Fattie
  • 27,874
  • 70
  • 431
  • 719
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
    }

} 
MANISH PATHAK
  • 2,602
  • 4
  • 27
  • 31
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.

Tamarisk
  • 929
  • 2
  • 11
  • 27
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
Community
  • 1
  • 1
Artem Novichkov
  • 2,356
  • 2
  • 24
  • 34