Why is UIFont sizeWithFont including blank space in its calculation? - ios

I am setting a UILabels frame based on what is returned by UIFont sizeWithFont but for whatever reason when i use a custom font, the values that are returned include some padding as seen below.
When i use boldSystemFontOfSize the text is vertically aligned in the middle (which is what i want), but when i use fontWithName i end up with padding under the text. Any reason why sizeWithFont is adding in the padding?
Heres my code...
CGRect frameLabel = label.frame;
CGSize sizeLabel = [label.text sizeWithFont:label.font];
frameLabel.size.width = sizeLabel.width;
frameLabel.size.height = sizeLabel.height;
[label setBackgroundColor:[UIColor redColor]];
** Edit **
I can calculate the top and bottom padding using this code and adjust the labels frame.origin.y to vertically center my label where it needs to be
float topPadding = [label.font ascender] - [label.font capHeight];
float bottomPadding = [label.font lineHeight] - [label.font ascender];

Font is the only possible cause of this padding, but if you only need one-line labels, don't waste your time editing the font, just reduce the label's height by those few pixels after setting a proper frame by doing something like this:
label.frame = CGRectInset(label.frame, 0, bottomPadding);
Also, instead of:
CGRect frameLabel = label.frame;
CGSize sizeLabel = [label.text sizeWithFont:label.font];
frameLabel.size.width = sizeLabel.width;
frameLabel.size.height = sizeLabel.height;
You can just call:
[label sizeToFit];

Related

How to programmatically sizeToFit width AND height on UILabel?

I'm programmatically creating multi-line UILabels ([label setNumberOfLines:0];).
The built-in sizeToFit method of UILabel works great for 1 line UILabels, but for multi-line text, it sets the height properly, but the width is set too small, causing longer text lines to wrap.
I don't know the label width until after the user enters their text. I want to resize the labels to fit the width of the longest line of text. And per #DonMag's comment, I also want to restrict the label to not be wider than the screen.
I tried different lineBreakMode settings but there isn't a 'nowrap' option.
I've searched SO and there are many related solutions but none that solve the problem of sizeToFit for both width and height.
Is there a way to programmatically size a multi-line UILabel to fit BOTH the width AND the height of the text?
You can do this with boundingRectWithSize...
Add your label to the view and give it a starting width constraint (doesn't really matter what value, as it will be changed).
Keep a reference to that width constraint (IBOutlet works fine if you're using IB).
Don't give it a height constraint.
When you set the text of the label, you can use this to change its width:
// get the font of the label
UIFont *theFont = _theLabel.font;
// get the text of the label
NSString *theString = _theLabel.text;
// calculate the bounding rect, limiting the width to the width of the view
CGRect r = [theString boundingRectWithSize:CGSizeMake(self.view.frame.size.width, CGFLOAT_MAX)
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
attributes:#{NSFontAttributeName: theFont}
context:nil];
// change the constant of the constraint to the calculated width
_theWidthConstraint.constant = ceil(r.size.width);
// NOTE: If you are *not* using auto-layout,
// this same calculation can be used to explicitly set
// the .frame of the label.
Edit:
As per the OP's requirement, a complete, runnable example -- using code only, no storyboards / IB -- can be found here: https://github.com/DonMag/MultilineLabelFitWidth
Edit 2:
GitHub project updated... now includes examples for both manual frame setting and auto layout / constraints.
With some more experimentation, I found something that does the trick that I have not seen in SO (yet...). In general it works like this:
Find the longest text line
Set numberOfLines to 1 (temporarily)
Set label text to longest text line
Call label.sizeToFit (sets label width for longest line)
Set numberOfLines to 0 (multi-line)
Set label text to full multi-line text
Call label.sizeToFit (sets label height for all lines)
Voila! Now your UILabel is sized to fit your multi-line text.
Here is an example (demo project on GitHub: UILabelSizeToFitDemo):
- (UILabel *)label = nil;
- (void)updateLabel:(NSString *)notes {
// close to the "sticky" notes color
UIColor *bananaColor = [ViewController colorWithHexString:#"#FFFC79"];
if (_label == nil) {
_label = [[UILabel alloc] init];
_label.numberOfLines = 0;
_label.textColor = UIColor.blackColor;
[_label setBackgroundColor:[bananaColor colorWithAlphaComponent:0.9f]];
_label.textAlignment = NSTextAlignmentLeft;
[self.view addSubview:_label];
}
// make font size based on screen size
CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height;
CGFloat fontSize = MIN(screenWidth,screenHeight) / 12;
[_label setFont:[UIFont systemFontOfSize:fontSize]];
// split lines
NSArray *lines = [notes componentsSeparatedByString:#"\n"];
NSString *longestLine = lines[0]; // prime it with 1st line
// fill a temp UILabel with each line to find the longest line
for (int i = 0; i < lines.count; i++) {
NSString *line = (NSString *)lines[i];
if (longestLine == nil || line.length > longestLine.length) {
longestLine = line;
}
}
// force UILabel to fit the largest line
[_label setNumberOfLines:1];
[_label setText:longestLine];
[_label sizeToFit];
// make sure it doesn't go off the screen
if (_label.frame.size.width > screenWidth) {
CGRect frame = _label.frame;
frame.size.width = screenWidth - 20;
_label.frame = frame;
}
// now fill with the actual notes (this saves the previous width)
[_label setNumberOfLines:0];
[_label setText:notes];
[_label sizeToFit];
// center the label in my view
CGPoint center = CGPointMake(self.view.bounds.size.width / 2, self.view.bounds.size.height / 2);
[_label setCenter:center];
}
UPDATE: Here is an alternate complete solution, using the boundinRectWithSize from the code snippet by #DonMag:
-(void)updateLabel:(NSString *)notes {
// close to the "sticky" notes color
UIColor *bananaColor = [ViewController colorWithHexString:#"#FFFC79"];
if (_label == nil) {
_label = [[UILabel alloc] init];
_label.numberOfLines = 0;
_label.textColor = UIColor.blackColor;
_label.backgroundColor = [bananaColor colorWithAlphaComponent:0.9f];
_label.textAlignment = NSTextAlignmentLeft;
[self.view addSubview:_label];
}
// set new text
_label.text = notes;
// make font size based on screen size
CGFloat screenWidth = [UIScreen mainScreen].bounds.size.width;
CGFloat screenHeight = [UIScreen mainScreen].bounds.size.height;
CGFloat fontSize = MIN(screenWidth,screenHeight) / 12;
[_label setFont:[UIFont systemFontOfSize:fontSize]];
// calculate the bounding rect, limiting the width to the width of the view
CGRect frame = [notes boundingRectWithSize:CGSizeMake(self.view.frame.size.width, CGFLOAT_MAX)
options:(NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading)
attributes:#{NSFontAttributeName: _label.font}
context:nil];
// set frame and then use sizeToFit
[_label setFrame:frame];
[_label sizeToFit];
// center the label in my view
CGPoint center = CGPointMake(self.view.frame.size.width / 2, self.view.frame.size.height / 2);
[_label setCenter:center];
}

UILabel.frame.size.height shows less height than actual value

I am creating a UILabel with dynamic height by calculating the length of the text with this code:
UILabel *bankFullName = [[UILabel alloc] init];
[bankFullName setLineBreakMode:NSLineBreakByWordWrapping];
[bankFullName setMinimumScaleFactor:1];
[bankFullName setNumberOfLines:0];
[bankFullName setFont:[UIFont systemFontOfSize:13]];
CGSize constraint1 = CGSizeMake(240.0f, 20000.0f);
CGSize size1 = [[bankDetail valueForKey:#"FullName"] sizeWithFont:[UIFont systemFontOfSize:13] constrainedToSize:constraint1 lineBreakMode:NSLineBreakByWordWrapping];
bankFullName.text = [bankDetail valueForKey:#"FullName"];
CGRect fullnameFrame = CGRectMake(15.0, 15.0, 240.0, size1.height);
bankFullName.frame = fullnameFrame;
Now I don't have any issue with the code and the label is perfect.
The problem is when I log bankFullName.frame.size.height, the height is not right. If the label has two lines, it gives the height of the label having one line and so on.
Any idea on how to get the accurate height of the label or if something I'm doing is wrong?
Thanks
Just log the height of label in this method as -[UIView layoutSubviews] get called when the size of the any view changes:-
-(void)layoutSubviews
{
[super layoutSubviews];
NSLog(#""... // log your label height to get the exact value.
}
Do not use sizeWithFont: to change the height of your label.
Use [bankFullName sizeToFit] instead. And don't forget to set the maximum number of lines to more than one.
You should use sizeThatFits: to get correct size for UILabel, because sizeWithFont and boundingRectWithSize:options:attributes:context don't include UILabel padding.
CGFloat height = [self.bankFullName sizeThatFits:CGSizeMake(240, MAXFLOAT)].height;
self.bankFullName.frame = CGRectMake(15, 15, 240, height);

Manage label text dynamically not work properly

CGFloat constrainedSize = 500.0f;
UIFont * myFont = [UIFont fontWithName:#"Arial" size:19]; //or any other font that matches what you will use in the UILabel
CGSize textSize = [myText sizeWithFont: myFont
constrainedToSize:CGSizeMake(constrainedSize, CGFLOAT_MAX)
lineBreakMode:NSLineBreakByWordWrapping];
lblDescription=[[UILabel alloc]initWithFrame:CGRectMake(10,y, 300,textSize.height)];
i have tried this code for manage the dynamic text. but if the data comes larger it will not display the whole text.
You constrain the size to width = 500pt, but your textfield is only 300pt wide.
Edit:
It seems, I wasn't clear. If you calculate the height of the label with sizeWithFont and give as constraint a width of 500pt (constrainedSize) and use the calculated height then on a label with only 300pt width, the calculated height is not correct.
This is how it works for me:
CGFloat constrainedSize = 300.0f;
UIFont * myFont = [UIFont fontWithName:#"Arial" size:19]; //or any other font that matches what you will use in the UILabel
CGSize textSize = [myText sizeWithFont: myFont
constrainedToSize:CGSizeMake(constrainedSize, CGFLOAT_MAX)
lineBreakMode:NSLineBreakByWordWrapping];
UILabel* lblDescription=[[UILabel alloc]initWithFrame:CGRectMake(10.0, 10.0, constrainedSize, textSize.height)];
lblDescription.lineBreakMode = NSLineBreakByWordWrapping;
lblDescription.numberOfLines = 0;
lblDescription.font = myFont;
lblDescription.text = myText;
Again: use the same attributes for the label (font, size, lineBreakMode) as you use for the calculation, otherwise it won't fit.
That's because you are alloc and initing the UILabel, and never reset it's frame for bigger size text.
Simply set the frame of the UILabel after a text size change.
This line sets the size of the label, to textSize.height and it doesn't change.
lblDescription=[[UILabel alloc]initWithFrame:CGRectMake(10,y, 300,textSize.height)];
You can call setFrame to programmatically change this.
To display a larger text/Multiline text you should use UITextView instead. It is designed to handle this issue.
hi here is code to set dynamic text in label
CGSize maximumLabelSize = CGSizeMake(300,9999);
CGSize expectedLabelSize = [label.text sizeWithFont:label.font
constrainedToSize:maximumLabelSize
lineBreakMode:label.lineBreakMode];
CGRect newFrame = label.frame;
newFrame.size.height = expectedLabelSize.height;
label.frame = newFrame;
i hope this is helping to you..

UILabel with "+" text not centered vertically

self.iconLabel.frame = self.contentView.frame;
self.iconLabel.font = [UIFont systemFontOfSize:100.0];
self.iconLabel.text = #"+";
self.iconLabel.textColor = [UIColor blackColor];
self.iconLabel.textAlignment = NSTextAlignmentCenter;
So the UILabel's frame is the same size as its container view, self.contentView
I need the "+" to be centered vertically also, I want it in the center.
How can I achieve this?
My only notion is to shift the iconLabel.frame up.
EDIT: Changed from self.iconImageView to self.contentView which is a UIView. The UILabel is layered above the self.iconImageView which is a UIImageView.
EDIT 2: Tried implementing kshitij godara's code, the result is:
I altered the code slightly:
CGSize constraint = CGSizeMake(self.contentView.frame.size.width, self.contentView.frame.size.height);
NSString *str = #"+";
UIFont *myFont = [UIFont systemFontOfSize:100.0];
CGSize stringSize = [str sizeWithFont:myFont constrainedToSize:constraint lineBreakMode:NSLineBreakByWordWrapping];
self.iconLabel.frame = CGRectMake(0, 0, stringSize.width, stringSize.height);
self.iconLabel.font = myFont;
self.iconLabel.lineBreakMode = NSLineBreakByWordWrapping;
self.iconLabel.numberOfLines = 0;
[self.iconLabel sizeToFit];
self.iconLabel.text = str;
EDIT 3:
the frame is quite large:
This is probably a problem of the font itself. Each font has defined it shapes, behavior, typesetting etc. The font seems to be positioning character "+" of the visual vertical center. You can test it on any other string containing letter "+", "dog+Cat" for example. The "+" letter will probably be drawn near bottom as it is on your image.
From my point of view, you have 2 options:
Use icon image instead for the "+" sign. This will be simplest and the best working solution
Use custom font edited to visually look exactly like you need (letter "+" totally centered in its typeset). This one would probably be very painfull and time-consuming option
Edit: Regarding 2nd option: I have already done such an procedure to fix some misaligned fonts following this tutorial. However it still just fixes the whole font, not on a single character basis
You can do this -- Try to apply this
CGSize constraint = CGSizeMake(iconImageFrame.size.width,1000.0f);
//Now Calculate size of string
NSString *str = #"+";
//Always give the same font to calculate size which you giving for label
UIFont *myFont = [UIFont systemFontOfSize:100.0];
CGSize stringSize = [str sizeWithFont:myFont
constrainedToSize:constraint
lineBreakMode:UILineBreakModeWordWrap];
//now set same font for uilabel and also make it to fit to size
//like this
UILabel *myLabel = [[UILabel alloc]initWithFrame:CGRectMake(myIconLabelFrame.x,myIconLabelFrame.y,stringSize.width,stringSize.height)];
myLabel.font = myFont;
myLabel.lineBreakMode = UILineBreakModeWordWrap;
myLabel.numberOfLines = 0;
[myLabelsizeToFit];
myLabel.text = str;
As now label will be completely fit in size , therefore it will always be in center .This code also can be used for to increase height of label dynamically .
I hope it solve your problem .Thanks!

Dynamically Creating and Positioning UILabels of Different Sizes

I am creating dynamic UILabels and then adding them to the view. The labels are in vertical layout. Like this:
Label 1
Label 2
Label 3
Each label can be of different height. So in order to accomodate the height I use the following code. The Label 2 uses Label 1 position and height to adjust itself on the view.
-(void) createAboutLabel
{
self.aboutLabel = [[UILabel alloc] initWithFrame:CGRectMake(self.nameLabel.frame.origin.x, self.nameLabel.frame.origin.y + self.nameLabel.frame.size.height, self.view.frame.size.width - 10, 40)];
self.aboutLabel.text = self.selectedVegetableCategory.about;
self.aboutLabel.font = [Utility getFont:#"Sinhala Sangam MN" size:14];
// [self.aboutLabel sizeToFit];
[self.aboutLabel setNumberOfLines:0];
[self.aboutLabel setLineBreakMode:UILineBreakModeWordWrap];
[self.aboutLabel sizeToFit];
[self.scrollView addSubview:self.aboutLabel];
}
I am trying to find a better way of achieving the dynamic label layout.
Any suggestions?
By passing width & font, height can be retrieved -
NSString *theText = myLabel.text;
CGFloat width = myLabel.frame.size.width ;
CGSize theSize = [theText sizeWithFont:myLabel.font constrainedToSize:CGSize(width,MAXFLOAT) lineBreakMode:UILineBreakModeWordWrap];
It will return the size from that extract height.
CGFloat myLabelHeight = theSize.height;
Hope this should help.

Resources