I am currently using
- (CGSize)sizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size lineBreakMode:(UILineBreakMode)lineBreakMode
to get the size of an NSString. However, when that string includes emojis, it seems to calculate the size for the literal unicode character rather than taking into account the size of the emoji itself, rendering the returned size incorrect.
How do I correctly get the size of the string with emoji characters, as it will appear in a uilabel?
The NSString is not presenting the emoji, it's representing a string, so the sizeWithFont will only account for the string.
I would use:
CGRect labelFrame = label.frame;
labelFrame.size = [label sizeThatFits:CGSizeMake(100, 9999)];
[label setFrame:labelFrame];
or
//Alternatively
[label sizeToFit];
Bare in mind that sizeToFit calls the sizeThatFits: method, so in terms of just setting the label to the right height, sizeThatFits: is quicker, and much easier on the eye.
I struggled with this same thing for a while, attempting multiple solutions including the accepted answer, which did not work for me. I solved this by creating an NSAttributed String with the text, then using the NSAttributedString method boundingRectWithSize:options:context: to get the size of the string.
NSString *text = //some text
CGFloat maxSize = //text size constraints
NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:text
attributes:#{NSFontAttributeName : font
}];
CGRect boundingRect = [attributedString boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin context:nil];
CGSize fitSize = boundingRect.size;
Related
A subquestion is:
How do I determine what the built-in internal margins of a UITextview are?
I have a long master string of text that I am trying to split into separate UITextView pages that I can then scroll from page to page inside a UIScrollView. I use the following method to determine what the height of a string in a UITextView is and whether the string is over the height limit:
-(NSNumber *)getHeightByWidth: (NSString *) myString
mySize: (UIFont *) mySize
myWidth: (NSNumber *) myWidth
{
int intMyWidth = [myWidth intValue];
CGSize boundingSize = CGSizeMake(intMyWidth, CGFLOAT_MAX);
CGSize requiredSize = [myString sizeWithFont:mySize constrainedToSize:boundingSize lineBreakMode:UILineBreakModeWordWrap];
NSNumber *retNumber = [[NSNumber alloc] initWithFloat:requiredSize.height];
return retNumber;
[retNumber release];
}
I call the getHeightByWidth method using the following cellFont as the input for mySize:
UIFont *cellFont = [UIFont fontWithName:#"Arial" size:14.0];
The UITextView is 320 pixels wide, but I notice that the text doesn't go from the left edge to the right edge as there are internal margins which look to be around 10 pixels on each side. So when I call getHeightByWidth I set myWidth = (320 - 10 - 10); But after building strings to fit within the UITextView, there are usually gaps on the last row that could be filled with the next words in the master string.
Can anyone tell me why these gaps on the last row of the text occur using this process for UITextView?
The built-in margins are represented by the property contentInset.
Also you can configure the margins yourself.
If you have your text view in IB, look for Content insets. The values must be 0, but it still displays some margin. Trying setting them to negative values such as -4 or -8.
In the code, do something like-
myTextView.contentInset = UIEdgeInsetsMake(-4,-8,0,0);
You have to set these values according to what you find suitable.
As I get strings of text down from my database, I need to know how big to make the UITextField and how big to make the Cell of the table that contains the UITextfield.
Are there any clever methods that can determine this? Since it will of course depend on the textfield width and the font used.
Thanks
-Code
NSString has a method sizeWithFont:contrainedToSize: that will give you to the size of a string with a particular font:
UIFont *font = [UIFont systemFontOfSize:12.f];
CGSize size = CGSizeMake(textView.frame.size.width, 1000.f);
CGSize stringSize = [myString sizeWithFont:font constrainedToSize:size];
Is there an equivalent to NSString's sizeWithFont: method that can be used for calculating the height of text in a UITectView for a given width? All of the methods from NSString only operate on a single line from what I can tell.
From Apple's reference for these NSString methods, you could use -sizeWithFont:constrainedToSize: or -sizeWithFont:constrainedToSize:lineBreakMode: for "Computing Metrics for Multiple Lines of Text".
CGSize size = [theString sizeWithFont:font
constrainedToSize:CGSizeMake(width, 100000)];
return size.height;
For UITextView, all you have to do is call -sizeToFit on the view, and it will automatically resize its height until it can fit all the text available. All you need to do is set the width of the text view, set the text, then call -sizeToFit. The text view will resize its height just enough to fit all the text.
UPDATE:
Apparently text views only shrink when there's excess height, but they don't grow if there's insufficient height to display all the text. In addition, once you call -sizeToFit, the text view's y coordinate is reset back to 0.0f. So here's what you do:
CGFloat textViewWidth = 300.0f;
CGFloat textViewPadding = 10.0f;
UITextView * textView = [[[UITextView alloc] init] autorelease];
textView.text = ...; // Really long string
textView.frame = CGRectMake(0.0f, 0.0f, textViewWidth, CGFLOAT_MAX);
[textView sizeToFit]; // Shrinks the height to fit all the text
textView.frame = CGRectMake(textViewPadding, textViewPadding,
textViewWidth, textView.frame.size.height);
[self.view addSubview:textView];
First, you set the frame just so you can set the width like you want it. You use CGFLOAT_MAX to pretty much indicate infinite height. Next, calling -sizeToFit shrinks the height until it just fits all the text. However, it also resets the y coordinate, so we go ahead and set the frame again to configure the x and y coordinates—in this example, 10.0f for both x and y—, leaving the width alone and keeping the height set to whatever -sizeToFit calculated.
actually, you could use the property contentSize.
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;
}
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!
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)
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.
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.
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));
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).
This general topic has been asked here multiple times: how to render UITableViewCells with varying amount of text and thus varying height. The canonical answer is: you calculate the height in table view controller delegate in heightForRowAtIndexPath using sizeWithFont:constrainedToSize:lineBreakMode:. Later, the cell gets drawn, and you use something like [label sizeToFit] if needed, and all works like magic.
My problem: I am getting wrapping for some cells because sizeWithFont: returns different dimensions from actual drawing.
A specific example:
The text is this: "People forget that #BillGates had a sexy 1/4-inch thick slate back in 1993 from NEC. Whatever happens this week will NOT be about hardware!"
CGSize theSize = [text sizeWithFont:[UIFont systemFontOfSize:17.0f] constrainedToSize:CGSizeMake(310.0f, FLT_MAX) lineBreakMode:UILineBreakModeWordWrap];
NSLog(#"calculated size for %#: %f, %f",text, theSize.width, theSize.height);
This returns: 306.000000, 84.000000. (I.e 4 rows with 17px font and 4px linespacing, 21px leading.) Good.
However, later when actually drawing the cell:
label = (UILabel *)[cell viewWithTag:3];
label.text = [NSString stringWithFormat:#"%#", text];
label.lineBreakMode = UILineBreakModeWordWrap;
label.font = [UIFont systemFontOfSize:17.0f];
CGSize labelSize;
labelSize = label.frame.size;
NSLog(#"label size before resizing: %f, %f", labelSize.width, labelSize.height);
[label sizeToFit];
labelSize = label.frame.size;
NSLog(#"label size after resizing: %f, %f for text %#", labelSize.width, labelSize.height,text);
(UILabel is loaded as part of UITableViewCell from NIB. In IB I set it to 310px wide.)
This should return exactly the same size as above. Instead, I get 281.000000, 105.000000 as the dimensions after sizeToFit call. It is now 5 lines at drawing time instead of 4, and the text spills over, I see the spillover in the UI.
So, for the same text, I am getting two different dimensions calculated, and can't figure it out. Is it something about UILabel? Does it have some inner margins? This keeps happening for some texts but not others, and I have not traced it to something particular about the strings; seems random. This topic highlights that there are two processing passes: calculating height vs actual drawing. This is consistent with what I'm seeing. But I don't understand what exactly is going on or how to fix it.
The question: why am I seeing two different calculated sizes, and how do I fix it?
Of course, the solution is obvious 30 seconds after posting. Maybe useful to others too...
The size by sizeWithFont: was correct. The sizes I calculated in the above way were incorrect, because [label sizeToFit] reduces the width of the label's frame. At subsequent calls to the same code, it started off with the frame that may already have been reduced.
The fix was to simply reset the frame width to a known good width before sizing to fit:
CGRect labelFrame = label.frame;
labelFrame.size.width = 310;
label.frame = labelFrame;
[label sizeToFit];
For multiline labels you need set
cell.textLabel.numberOfLines = 0;
and then
[cell.textLabel sizeToFit];
But for pretty view you need add some padding pixels. And your app will be awesome!
titleSize = [title sizeWithFont:[UIFont systemFontOfSize:(CGFloat)17.0]
constrainedToSize:CGSizeMake(280, 2000)
lineBreakMode:NSLineBreakByWordWrapping];