Fonts not fitting properly in UILabel - Ascender or Descender cropped - ios

I've done extensive searching/reading/testing and cannot find a solution to this problem.
I've tried since iOS 4.3 and it's still not resolved in iOS7.
The problem is this: Fonts at large sizes can have their Ascenders or Descenders cropped in a UILabel.
Here's a screenshot directly from the Xcode 5.1 UI (no code at all!) showing the problem - font size 300 points:
As you can see, even a simple font like Helvetica Neue (bold or not) has it's Descender cropped. (You're seeing UIViewController > UIView > UILabel)
If you try this and then change the point size you'll see the font scale down, and eventually the Descender will not be cropped. Here it is again at 160 points:
Notice also that some fonts do not get cropped and others do - try Noteworthy, or Papyrus, or Savoye LET - all of which are standard iOS & fonts....
I'm talking about Height here - I know I can use adjustsFontSizeToFitWidth=YES to see the entire length, and I also know I can use sizeToFit, however neither guarantees no cropping of the Ascender/Descender.
Notice also that calculating the height using Ascender/Descender values does not help as the main issue is that the font is not centered vertically within the label when it is drawn. (If it were, it would be a simple calculation.)
So here is the question: How can I show a font as tall as possible and be assured that the Ascender/Descender is not cropped regardless of the font used?
EDIT:
I re-read my question and realized I did not ask it properly - I'm able to resize the label to fit the font - that's not the problem. Here's the revised question:
How can I draw text in a UILabel as large as possible and be assured that it is centered vertically, with no cropping of the Ascender or Descender?
I can easily figure out the overall height of the text, and once I know it will fit, how can draw it in the UILabel vertically centered?
For Example: In the first screenshot, the text "Tg" is cropped, but it is easily short enough to fit vertically in the label. In fact, it could be even larger and still fit if it were properly centered. But I know of no way to center it vertically...

The size of the label can be sized according the length of the string, the font attribute used and the size of the font. I use this method a lot and works great for such requirements -
NSString *textWithinLabel = #"Whatever you like, passed from where ever";
CGSize maximumLabelSize = CGSizeMake(300, 1000); //Place your maximum sizes here
//Here I've used Helvetica, though you can pass any font name or font size here to try out
NSDictionary *stringAttributes = [NSDictionary dictionaryWithObject:[[UIFont fontWithName:#"Helvetica" size:15] forKey: NSFontAttributeName];
CGSize newExpectedLabelSize = [textWithinLabel boundingRectWithSize:maximumLabelSize options:NSStringDrawingTruncatesLastVisibleLine|NSStringDrawingUsesLineFragmentOrigin attributes:stringAttributes context:nil].size;
CGRect frame = self.yourLabel.frame;
frame.size.height = newExpectedLabelSize.height;
self.yourLabel.frame = frame;
This example will change the height of the label, though you can use it to change width too etc.
The stringAttributes here are used to calculate the size, not to set the attributes. So for example, if your label is using 14pts and you calculate the height for 30pts, it won't change the height of the font, it will only increase the size of the label to accommodate the larger font size. If you want this method to also change the font attributes, you would need to add the appropriate code at the bottom of the method - self.yourLabel.text.font = ... etc.
I hope this answers your question,
Thanks, Jim.

I tried this and it solved my problem. Essentially, the height of the letter is Ascent+Descent. So that's all the space the label needs vertically.
1. [commentLabel sizeToFit]; //To trim out the unwanted area from the label
2. [commentLabel setFrame:CGRectMake(commentLabel.frame.origin.x, commentLabel.frame.origin.y + ABS(commentLabel.font.descender), commentLabel.frame.size.width, commentLabel.font.ascender + ABS(commentLabel.font.descender))];
//The frame adjustment in **(2)**moves the label down by commentLabel.font.descender because the label by default is aligned based on their actual bottom line instead of the actual line we use on notebooks, where the descender hangs down from the line. In case of a label the bottom line is the lower tip of the descender.

Related

boundingRectWithSize sometimes returns height that is about 1 line too tall

I have looked at several Stackoverflow questions where people have problems with boundingRectWithSize, but none of them are quite my problem.
The following code often computes a height that is 1 line of text too tall.
CGFloat height = [label.attributedText
boundingRectWithSize:CGSizeMake(label.width, CGFLOAT_MAX)
options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
context:nil].size.height;
For example, the string Map Type: USGS Topo is about half the width of my view and should be 1-line tall (~18 pixels), but ends up having a 2-line height (~36 pixels):
I also tried calculating the height using the NSString instead of NSAttributedString, but the same thing happens:
CGFloat height = [[label.attributedText string]
boundingRectWithSize:CGSizeMake(label.width, CGFLOAT_MAX)
options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
attributes:#{NSFontAttributeName:TITLE_FONT}
context:nil].size.height;
You can see in this screenshot that the bottom 3 labels end up sized correctly, but not the top two. I turned on color blended labels in the iOS simulator so the subviews would be colored to illustrate:
You probably have a trailing newline in your string. Try trimming it with whitespaceAndNewlineCharacterSet.
I have had something similar happen to me awhile ago, though it was not with an attributed string. When it happened to me the lable's frame wasn't fully set yet and so at the time of the calculation the width was narrow enough that it had to wrap to the next line. Check the frame for your label when you do this calculation and make sure it is actually as wide as you think it is.

Replacement for minimumFontSize for a UILabel

I'm aware of minimumScaleFactor but it isn't useful for the result I'm after.
I have two UILabels and I want them both to be the same size. I want them to fit to the label's view if possible down to a minimum size and then I'll do a check to set them both to the smallest font size, so that they both fit and are both the same size.
I don't think I can achieve this using minimumScaleFactor because it doesn't seem to actually change the font size, it seems to use some sort of scaling on the view (correct me if I'm wrong). I don't seem to be able to set this scale manually other than setting it's minimum value so it seems to be useless in my circumstances.
Set max_msg_height that you want in your cell. This function will check if your message length is more than max_mgs_height and font size and if it is greater than 12 points, it will continue. In my case i have set max_msg_height = 160. Default font size to 16. So, if I got message which is not adjust in lable frame then I reduce font by 1 point by using this while loop as shown below:
while (messageLbl.frame.size.height > max_msg_height &&
messageLbl.font.pointSize>12) {
messageLbl.font = [UIFont systemFontOfSize:messageLbl.font.pointSize-1];
[messageLbl sizeToFit];
}

Ignore Ascender and Descender when centering UILabel vertically?

I’m using AutoLayout to position some labels in the vertical centre of a cell. The text is in all-caps, but the UILabel in question, even when sizeToFit is applied, leaves space below the text, which looks a lot like it would be for the tails on letters such as a lower case y, p, and q. Since I’m centring vertically, this is causing an offset and meaning the text appears a few pixels higher than it should do.
Another question may be: can I have a font intelligently adjust its vertical centre dependant on whether it contains any characters which use the ascender or descender?
For instance, the string “abbaba” doesn’t need the descender, whereas the string “oyyoyo” doesn’t need the ascender. Strings in all-caps also never need the descender. If I vertically center “oyyoyoyo” it’ll appear too low.
Thanks, Abhinit, for your answer.
I was also looking for this so I would like to post here the exact constraints you need to apply to align texts to your liking.
This image from Wikipedia shows the different size sections of a font.
So, there are many ways to align a label depending on whether you want to align to the ascender height, the cap height, the x-height, baseline or descender height.
Let's say you have a label containing text in caps like "HELLO" and you want to align with viewAbove to cap height and align with viewBelow to baseline.
You would do:
let font = label.font
let ascenderDelta = font.ascender - font.capHeight
LayoutHelper()
.addViews([
"label":label, "viewAbove":viewAbove, "viewBelow":viewBelow
])
.withMetrics(["ascenderDelta":ascenderDelta])
.addConstraints([
// -- Here the constraints to align to cap height --
"X:label.top == viewAbove.bottom - ascenderDelta",
"X:label.baseline == viewBelow.top",
...other constraints...
])
Note: in the example I'm using my utility class LayoutHelper, but I hope the idea is clear.
About an "auto-aligning" label:
I will think about making an "intelligent" label that adjusts to the appropriate line depending on whether it contains descenders, ascenders, caps, etc.
You could do it using negative insets in drawTextInRect(like here but, for example, using insets = {-ascenderDelta, 0, font.descender, 0}). But that would crop any ascenders/descenders in case you had. I would prefer to align to caps without cropping any possible ascender.
You can use the 'capHeight' and 'xHeight' properties on UIFont to get the correct height and use that to size the UILabel.
Of course this assumes that you know for sure if a string would be lowercase or uppercase only. If not, then you can override setText on a UILabel and check every time the function gets called.
I would also think of looking deeper into CoreText and implementing something like this http://www.zsiegel.com/2012/10/23/Core-Text-Calculating-line-heights/
I had the same problem and solved it by subclassing UILabel and changing its draw method:
- (void)drawRect:(CGRect)rect
{
CGRect capCenteredRect = CGRectMake(rect.origin.x, (self.font.leading-self.font.capHeight)*0.5f, rect.size.width, rect.size.height);
[super drawTextInRect:capCenteredRect];
}
In my case I needed to center to caps because the vertical centering of the UILabel is always some pixels off.
If you need to center vertically to lower case letters with descender you can change the rect to:
CGRectMake(rect.origin.x, (self.font.leading-self.font.xHeight)*0.5f+self.font.descender, rect.size.width, rect.size.height)

How to justify text perfectly on iOS 7

I'm trying to horizontally align text bits with different size and fonts.
Here is the result I'd like (made with scribus):
You can see that text is horizontally align perfectly (to the pixel).
I tried implementing this with a basic UITextView, and justifying the text. Here is what I got:
You can see that the text is not horizontally aligned to the pixel.
I tried centering and it's still not good enough:
If things were not hard enough, the first and third line are dynamic text... so their size changes.
I had a look at textkit but didn't find anything conclusive...
My leads are:
find best font size for text given the width.
space glyphs evenly so that the text bounding box is equal to the desired width.
but I have no clue how to do that.
Any leads ? thanks !
Have you tried NSString's boundingRectWithSize:options:attributes:context:?
CGRect rect = [string boundingRectWithSize:CGSizeMake(width, height)
options:NSStringDrawingUsesFontLeading
attributes:attributes
context:nil];
If you are also supporting iOS 6, use -[NSString sizeWithFont:constrainedToSize:lineBreakMode:]
CGSize textSize = [string sizeWithFont:uifont
constrainedToSize:maximumCGSize
lineBreakMode:lineBreakMode];
The sizeWithFont:constrainedToSize:lineBreakMode: method has been deprecated as of iOS 7.
Using one of these methods, you can find the line that is the widest and match the others to it. In your case of dynamic text, I would suggest against using UITextView to ensure text is correctly justified.

UILabel AutoResize cuts off the top part of the text

I have a UILabel which autoresizes along with its parent view. The label has AdjustsFontSizeToWidth turned on and has a minimum text size of 0 - so basically it tries to fit all the text into whatever size the UILabel is.
The problem I am having is that vertically the text gets cut off. So yes, the label is adjusting its font size to the width of the label but the text is too tall for the label and thus some of the text is getting cut off.
Is there anyway to work around this so that all of the text, the full height and full width are shown?
I attach an image to show what I mean. The red box is the parent view, the purple box is the UILabel.
Thanks for your help.
What you are adjusting automatically is the Width and not the Height. The Height is something you'll have to adjust manually based on the maximum font size you will use. If the maximum (assigned initial) font size fits in height, so will the smaller one's do, after they are automatically adjusted
I suspect that Lefteris is right, that minimum text size focuses on font size for the width of the control. Note, though, if you want it to resize the font to fit, you want a non-zero minFontSize. See minimizeFontSize notes. Also check out the various NSString UIKit Additions that can be used to get the size of the control necessary to fit your text, and programmatically adjust the size (i.e. the frame) your UILabel accordingly.
In my case there was a bogus vertical centering of a view under the labels being clipped and squashed. That somehow took priority over compression resistance priority of 1000 for the labels. No warning on console about conflict though. But the view debugger was of some help.

Resources