15

Is there a way to get the correct size of an NSString using:

- (CGSize)sizeWithFont:(UIFont *)font forWidth:(CGFloat)width lineBreakMode:(UILineBreakMode)lineBreakMode

that doesnt get thrown off by 2 or 3 hundred character strings. At the moment if I try to use this method on these long strings it incorrectly calculates them and I end up with lots of whitespace at the bottom of the UITextView.

I've tried using UILineBreakModeWordWrap and UILineBreakModeCharacterWrap.

the resizing is being done in

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
    CGFloat     result = 44.0f;
    NSString*   text = nil;
    CGFloat     width = 0;
    CGFloat     tableViewWidth;
    CGRect      bounds = [UIScreen mainScreen].bounds;

    tableViewWidth = bounds.size.width;


    width = tableViewWidth - 150;       

    text = stringWithLongWords;

    if (text) {
        CGSize      textSize = { width, 20000.0f };     
        CGSize      size = [text sizeWithFont:[UIFont systemFontOfSize:10.0f] constrainedToSize:textSize lineBreakMode:UILineBreakModeWordWrap];

        size.height += 50.0f;               
        result = MAX(size.height, 44.0f+30.0f); 
    }

    return result;
}
  • What exactly are you trying to do? I think sizeWithFont: works pretty well, maybe it is how you apply that info to the UITextView. Please explain. – Stefan Arentz Feb 25 '10 at 01:42
  • I have a custom UITableView Cell with has 2 UILabels and a UITextView using sizeWithFont to calculate the size needed for the height the cell and the cell at times ends up being over 400 pixels too high. –  Feb 25 '10 at 02:38

6 Answers6

21

UITextView is not exactly like a UILabel wrapped in a UIScrollView. It has line spacing different from the font size and margins that sizeWithFont:constrainedToSize:linkBreakMode: doesn't account for.

Knowing your font size you might be able to calculate the # of lines and take line spacing into account. You can guess at the margins and try to trick sizeWithFont: to give a more useful answer.

The popular solutions seem to be:

  • just use a UILabel if you don't need any UITextView functionality

  • if you need hyperlinks, overlay UIButtons that look like hyperlinks over a UILabel

  • use an off-screen UITextView and its sizeToFit method to get a real answer

I had no luck w/ the 3rd option but it sounds like it should work, so perhaps I did something wrong.

I'm going to try using a UILabel and overlaying buttons for hyperlinks. We'll see how that turns out.

If that fails, there is always the option taken by Loren Brichter (of Tweetie fame): draw everything into a UIView yourself using CoreGraphics.

Good luck!

Girish
  • 4,692
  • 4
  • 35
  • 55
Sami Samhuri
  • 1,540
  • 17
  • 21
  • 2
    The third option probably isn't working because sizeToFit won't work until the UITextView is rendered to the screen. Just a thought. – Quentamia May 14 '12 at 15:57
  • Interesting. I wonder if you could render it on screen but with alpha = 0 or something very low to trick it into rendering but still not visible to the human eye. – Sami Samhuri May 15 '12 at 17:24
  • That might work and would be an interesting experiment. I'd love to find a way to correctly calculate the height before rendering though. It would be very useful when UITextViews are used within UITableViewCells. – Quentamia May 15 '12 at 17:55
  • +1: I had the exact same problem, that depending on the text the size would vary 20-30 pixels. So using a `UILabel` worked fine for me. – Besi Nov 30 '12 at 14:04
  • 1
    3rd option only worked for me in ios6. Looks related to http://stackoverflow.com/questions/18859637/setting-uitextview-frame-to-content-size-no-longer-works-in-xcode-5 Something equivalent to sizeWithFont: is talked about here: http://stackoverflow.com/questions/18903304/deprecated-in-ios-7-sizewithfont-constrainedtosize-linebreakmode-how-can – qix Oct 27 '13 at 09:37
  • 1
    Actual solution (and cause of the problem) here: http://stackoverflow.com/a/27322396/2057171 – Albert Renshaw Dec 05 '14 at 18:33
9

Check out this post How do I size a UITextView to its content?

It looks like textView.contentSize.height should work (with the caveat that the the correct contentSize is only available after the UITextView has been added to the view with addSubview)

Community
  • 1
  • 1
herbrandson
  • 2,357
  • 2
  • 30
  • 44
1

The best solution I have found so far is to have a separate hidden UITextView with the same font settings, and set its text. After that its contetSize should be accurate.

Nikola Lajic
  • 3,985
  • 28
  • 34
1

You said that you have a UITableView with differing heights. Have you set the reuse identifier to the same thing for all of the cells? It could be that older cells with their height already set are being reused. If this is the problem, you should resize the cell again when it's being reused.

nevan king
  • 112,709
  • 45
  • 203
  • 241
  • There is only one cell of this type on a UITableView, I checked to see if this was an issue and it doesn't seem to be. –  Feb 25 '10 at 03:54
1

The width you are using is the width for your UITextView... but you aren't concerned with that width, you are concerned with the width of the actual text area nested inside the text view.

UITextViews, by default, have padding around their borders to produce a space in-between the typed text and the edge of the UITextView a few pixels wide (and long for the top)... To get the correct size you shouldn't use

textView.frame.size.width

but rather,

textView.frame.size.width-(textView.contentInset.left+textView.contentInset.right+textView.textContainerInset.left+textView.textContainerInset.right+textView.textContainer.lineFragmentPadding/*left*/+textView.textContainer.lineFragmentPadding/*right*/)

^Which takes the width of the UITextView and subtracts out all the padding so you are left with the width of just the type-able text area.

Same goes for height except for lineFragmentPadding doesn't have a bottom so you only subtract it out once instead of twice.


The final code is something like this:

    CGSize textViewContentSize = CGSizeMake(theTextView.frame.size.width-(theTextView.contentInset.left+theTextView.contentInset.right+theTextView.textContainerInset.left+theTextView.textContainerInset.right+theTextView.textContainer.lineFragmentPadding/*left*/+theTextView.textContainer.lineFragmentPadding/*right*/), theTextView.frame.size.height-(theTextView.contentInset.top+theTextView.contentInset.bottom+theTextView.textContainerInset.top+theTextView.textContainerInset.bottom+theTextView.textContainer.lineFragmentPadding/*top*//*+theTextView.textContainer.lineFragmentPadding*//*there is no bottom padding*/));

    CGSize calculatedSize = [theTextView.text sizeWithFont:theTextView.font
                                          constrainedToSize:textViewContentSize
                                              lineBreakMode:NSLineBreakByWordWrapping];


    CGSize adjustedSize = CGSizeMake(ceilf(calculatedSize.width), ceilf(calculatedSize.height));
Albert Renshaw
  • 17,282
  • 18
  • 107
  • 195
0

Inspired by @MrNickBarker's answer, here's my solution:

CGFloat width = 280.0f;

UITextView *t = [[UITextView alloc] init];
[t setFont:[UIFont systemFontOfSize:17]];
[label setText:@"some short or long text, works both"];

CGRect frame = CGRectMake(0, 0, width, 0);
[t setFrame:frame];
// Here's the trick: after applying the 0-frame, the content size is calculated and can be used in a second invocation
frame = CGRectMake(0, 0, width, t.contentSize.height);
[t setFrame:frame];

The only issue remaining for me is that this doesn't work with modified insets.

Still can't believe such twists are required, but since -[NSString sizeWithFont:forWidth:lineBreakMode:] does not respect insets, paddings, margins, line spacings and the like, it seems this is the only working solution at the moment (i.e. iOS 6).

domsom
  • 3,163
  • 1
  • 22
  • 27