Anonymous White
Anonymous White

Reputation: 2159

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

[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?

Upvotes: 39

Views: 39685

Answers (10)

Tamarisk
Tamarisk

Reputation: 939

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.

Upvotes: 1

ndduong
ndduong

Reputation: 439

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
    }
}

Upvotes: 5

Mundi
Mundi

Reputation: 80271

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

Upvotes: 41

Fattie
Fattie

Reputation: 12277

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.)

Upvotes: 2

MANISH PATHAK
MANISH PATHAK

Reputation: 2660

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
    }

} 

Upvotes: 1

Artem Novichkov
Artem Novichkov

Reputation: 2341

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

Upvotes: 0

dijipiji
dijipiji

Reputation: 3099

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
    }
}

Upvotes: 2

vadimtrifonov
vadimtrifonov

Reputation: 818

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;

Upvotes: 44

de.
de.

Reputation: 8527

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!

Upvotes: 2

chrilith
chrilith

Reputation: 260

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.

Upvotes: 17

Related Questions