Determine Font Size After Autoshrink - ios

I have 2 UILabels for a scorekeeping app (Home and Away). The user can tap on Home or Away and change the label to be whatever they like. This is all working fine. When a user enters a name larger than the label size it will shrink the font to fit. This makes the 2 labels no longer match in font size, and doesn't look right. My question: How can you set the font size of one label to the size of another label that has been "autoshrunk"?
I also realize I will need to write code to determine which font size is smaller between the two labels and set them both to that number. I don't think I will have a problem with that, as long as I get an answer to the above question. It seems like it should be simple, but it has so far eluded me.

So I hope you know the frame size of both the labels.
Use – sizeWithFont:minFontSize:actualFontSize:forWidth:lineBreakMode: to find the minimum font size that it would use.
If you have any doubt, please let me know.

For me this worked fine:
- (CGFloat)actualTitleFontSize
{
NSStringDrawingContext *labelContext = [NSStringDrawingContext new];
labelContext.minimumScaleFactor = 0.2;
NSAttributedString *attributedString =
[[NSAttributedString alloc] initWithString:label.text attributes:#{NSFontAttributeName : label.font}];
[attributedString boundingRectWithSize:label.frame.size
options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading
context:labelContext];
CGFloat actualFontSize = label.font.pointSize * labelContext.actualScaleFactor;
return actualFontSize;
}

Related

Find width of attributed text in a UILabel when the text has multiple lines

I have multiline attributed text in a UILabel that I would like to find the width of. The purpose of this is to resize the font size for different devices. To do this I check whether the text with line breaks will fit a given space. I have tried text.size.width, however this will give the width as if the text is taking up one line.
Try this solution:
Here we get the size the entire NSAttributedString
CGRect paragraphRect =
[attributedText boundingRectWithSize:CGSizeMake(300.f, CGFLOAT_MAX)
options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
context:nil];
You can then use it to find the width of the NSAttributedString
Hope this helps you out.

How to adjust UILabel font size to fit the fixed Rectangle (for iOS7 +)?

I know this question has been asked so many times and I have seen most of the solutions like
How to adjust font size of label to fit the rectangle?
How to calculate actual font point size in iOS 7 (not the bounding rectangle)?
NSString sizeWithFont: alternative in iOS7
What I want is that I want to adjust the font size in a given rect with WORD WRAPPING technique. The posts I mentioned above somehow adjust the font size but none of them is using word wrapping technique. Let say, I have a label
UILabel *tempLabel = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 60, 25)]; \\60px width
tempLabel.text = #"Internationalisation";
tempLabel.numberOfLines = 2;
Now it should wrap the text in one line (within 60 px) with minimum possible font. But it keeps the text in 2 lines with a bit bigger font and the text breaks up like "Internationali" in first line and "sation" in second. How can we apply word wrapping in it.
tempLabel.adjustsFontSizeToFitWidth = YES;
does not seem to work for two or more lines and
CGSize size = [self sizeWithFont:font
minFontSize:minFontSize
actualFontSize:&actualFontSize
forWidth:maxWidth
lineBreakMode:self.lineBreakMode];
is deprecated in iOS7+
So how can I resolve my Issue. Please do help. This is so important for me.

Display various size of text block in Xcode

I'm creating a questionnaire app. In the view controller, I have a question displayed as a label and then I have three answers below displayed as labels too. The problem is that if the question has multiple lines, it's being displayed in one line anyway, and if I stretch out the label, it overlaps with the answers.
What is the right way to display the question so that the answers appear just under the question, no matter how long the question is?
Try setting the property numberOfLines to 0 for your question label, it will allow an unlimited number of lines to be displayed. Then calculate the text size using iOS 7 method boundingRectWithSize:options:context::
CGRect questionFrame = self.questionLabel.frame
CGFloat maxWidth = questionFrame.size.width;
NSString *questionText = [NSString stringWithString:#"lorem ipsum"]; // your text
UIFont *font = self.questionLabel.font; // your label font
// Temporary attributed string
NSAttributedString *attributedText = [[NSAttributedString alloc] initWithString:questionText attributes:#{ NSFontAttributeName: font }];
CGRect rect = [attributedText boundingRectWithSize:(CGSize){ maxWidth, CGFLOAT_MAX }
options:NSStringDrawingUsesLineFragmentOrigin
context:nil];
// Assign the new height
questionFrame.size.height = rect.size.height;
self.questionLabel.frame = questionFrame;
Then move your answers label one by one under the question label:
CGRect answerFrame = self.answerLabel1.frame;
answerFrame.origin.y = questionFrame.origin.y + questionFrame.size.height;
self.answerLabel1.frame = answerFrame;
answerFrame.origin.y = answerFrame.origin.y + answerFrame.size.height;
self.answerLabel2.frame = answerFrame;
answerFrame.origin.y = answerFrame.origin.y + answerFrame.size.height;
self.answerLabel3.frame = answerFrame;
If you want to use UILabel you must first of all set its "numberOfLines" property to 0. The value "0" means "no limit", so the text will be broken in the right number of lines. Then you have the problem of determining the right size of the label, as the height changes depending on the length of the question. In such case you can use the function - (CGRect)boundingRectWithSize:(CGSize)size options:(NSStringDrawingOptions)options context:(NSStringDrawingContext *)context (available with iOS7 and that replaces the - (CGSize)sizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size lineBreakMode:(NSLineBreakMode)lineBreakMode available before iOS7).
Once you determine the height of the label you change its frame accordingly. Obviously you must change the origin of the answers labels to avoid that they conflict with the question label.
Note that if both questions and answers are too long, you may end up with an overall labels height taller than the screen height. In such case you should put all these labels inside a scrollview or as cells of a table view, to allow scrolling.

Vertically Align UILabel text with constraints and no wrap (auto layout, single line)

So I have my view setup in IB such that this text label aligns with the top of the thumbnail via constraints.
However as we know, you can't vertically align text in a UILabel. My text updates the font size based on the length of the content. Full size text looks great, while small text is significantly lower on the view.
The existing solution involves either calling sizeToFit or updating the frame of the uilabel to match the height of the text. Unfortunately the latter (albeit ugly) solution doesn't play well with constraints where you aren't supposed to update the frame. The former solution basically doesn't work when you need to have the text autoshrink until it truncates. (So it doesn't work with a restricted number of lines and autoshrink).
Now as to why the intrinsic size (height) of the uilabel doesn't update like the width does when it's set to it's natural size via "Size to fit content" is beyond me. Seems like it definitely should, but it doesn't.
So I'm left looking for alternative solutions. As far as I can see, you might have to set a height constraint on the label, and adjust the height constant after calculating the height of the text. Anyone have a good solution?
This problem is a real PITA to solve. It doesn't help that the API's that work are deprecated in iOS7, or that the iOS7 replacement API's are broken. Blah!
Your solution is nice, however it uses a deprecated API (sizeWithFont:minFontSize:actualFontSize:forWidth:lineBreakMode:), and it's not very well encapsulated - you need to copy this code around to any cells or views where you want this behavior. On the plus side it's fairly efficient! One bug may be that the label hasn't been laid out yet when you do your calculation, but you perform your calculation based on its width.
I propose that you encapsulate this behavior in a UILabel subclass. By placing the sizing calculation in an overridden intrinsicContentSize method the label will auto-size itself. I wrote the following, which incorporates your code that will execute on iOS6, and my version using non-deprecated API's for iOS7 or better:
#implementation TSAutoHeightLabel
- (CGSize) intrinsicContentSize
{
NSAssert( self.baselineAdjustment == UIBaselineAdjustmentAlignCenters, #"Please ensure you are using UIBaselineAdjustmentAlignCenters!" );
NSAssert( self.numberOfLines == 1, #"This is only for single-line labels!" );
CGSize intrinsicContentSize;
if ( [self.text respondsToSelector: #selector( boundingRectWithSize:options:attributes:context: )] )
{
NSStringDrawingContext* context = [NSStringDrawingContext new];
context.minimumScaleFactor = self.minimumScaleFactor;
CGSize inaccurateSize = [self.text boundingRectWithSize: CGSizeMake( self.bounds.size.width, CGFLOAT_MAX )
options: NSStringDrawingUsesLineFragmentOrigin
attributes: #{ NSFontAttributeName : self.font }
context: context].size;
CGSize accurateSize = [self.text sizeWithAttributes: #{ NSFontAttributeName : [UIFont fontWithName: self.font.fontName size: 12.0] } ];
CGFloat accurateHeight = accurateSize.height * inaccurateSize.width / accurateSize.width;
intrinsicContentSize = CGSizeMake( inaccurateSize.width, accurateHeight);
}
else
{
CGFloat actualFontSize;
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
[self.text sizeWithFont: self.font
minFontSize: self.minimumFontSize
actualFontSize: &actualFontSize
forWidth: self.frame.size.width
lineBreakMode: NSLineBreakByTruncatingTail];
#pragma GCC diagnostic pop
CGRect lineBox = CTFontGetBoundingBox((__bridge CTFontRef)([UIFont fontWithName: self.font.fontName size: actualFontSize]));
intrinsicContentSize = lineBox.size;
}
return intrinsicContentSize;
}
#end
This implementation isn't perfect. I had to ensure using baselineAdjustment == UIBaselineAdjustmentAlignCenters, and I'm not 100% certain I understand why. And I'm not happy with the hoops I had to jump through to get an accurate text height. There's also a few pixel difference between what my calculation produces, and yours. Feel free to play with it and adjust as necessary :)
The boundingRectWithSize:options:attributes:context API seems pretty broken to me. While it (mostly!) correctly constrains the text to the input size, it doesn't calculate the correct height! The height it returns is based on the line-height of the supplied font, even if a scaling is in play. My guess is this is why UILabel doesn't have this behavior by default? My workaround is to calculate an unconstrained size where both the height and width are accurate, then use the ratio between the constrained and unconstrained widths to calculate the accurate height for the constrained size. What a PITA. There are lots of complaints in the Apple dev forums and here on SO that point out that this API has a number of issues like this.
So I found a workaround. It's a little dicey, but it works.
So what I did was add a height constraint to my line of text in IB, and grab a reference to that in my view.
Then in layoutSubviews, I update my constraint height based on the size of the font, which I have to calculate:
- (void)layoutSubviews {
if (self.titleLabel.text) {
CGFloat actualFontSize;
CGSize titleSize = [self.titleLabel.text sizeWithFont:self.titleLabel.font minFontSize:9.0 actualFontSize:&actualFontSize forWidth:self.titleLabel.frame.size.width lineBreakMode:NSLineBreakByTruncatingTail];
CGRect lineBox = CTFontGetBoundingBox((__bridge CTFontRef)([UIFont fontWithName:#"ProximaNova-Regular" size:actualFontSize]));
self.titleHeightConstraint.constant = lineBox.size.height;
}
[super layoutSubviews];
}
At first I was just setting it to the actual font size, but even with an adjustment (*1.2) it was still clipping the smaller font sizes. The key was using CTFontGetBoundingBox with the font size determined from my calculation.
This is pretty unfortunate, and I'm hoping there's a better way. Perhaps I should switch to wrapping.
TomSwift thanks for your answer, i really struggled with this issue.
If someone is still getting weird behaviour, i had to change:
intrinsicContentSize = CGSizeMake( inaccurateSize.width, accurateHeight);
to
intrinsicContentSize = CGSizeMake( inaccurateSize.width, accurateHeight * 2);
then it worked like charm.
What you're looking for is these two lines of code.
myLabel.numberOfLines = 0;
myLabel.lineBreakMode = UILineBreakModeWordWrap;
and you will also find this in the Attributes Inspector under "Line Breaks" and "Lines".

determining UIFont's pointSize for a specific CGSize

I have a specific CGSize of a UILabel, where I cannot expand the frame of UILabel and since it is a multi-line UILabels adjustsFontSizeToFitWidth method does not work.
So I figured I should create such function which looked like;
- (CGFloat)fontSizeWithText:(NSString*)text andFont:(UIFont*)font constrainedSize:(CGSize)size LBM:(UILineBreakMode)LBM
{
// check if text fits to label
CGSize labelSize = [text sizeWithFont:font constrainedToSize:CGSizeMake(size.width, 9999) lineBreakMode:LBM];
// if not, decrease font size until it fits to the given size
while (labelSize.height > size.height) {
font = [UIFont fontWithName:font.fontName size:font.pointSize - 0.5];
labelSize = [text sizeWithFont:font constrainedToSize:CGSizeMake(size.width, 9999) lineBreakMode:LBM];
}
return font.pointSize;
}
Usage:
// fit detail label by arranging font's size
CGFloat fontSize = [self fontSizeWithText:self.titleLabel.text andFont:self.titleLabel.font constrainedSize:self.titleLabel.frame.size LBM:self.titleLabel.lineBreakMode];
self.titleLabel.font = [UIFont fontWithName:self.titleLabel.font.fontName size:fontSize];
But with this method, I see that some of my texts doesn't fit to the UILabel s frame and gets truncated. There must be something I am missing. Any help for the code or any other suggestions on resolving "fitting a text with given font to a specific multi-line UILabel" would be great.
First lets quickly take a look at the problem as a whole. You're trying to fit text into a predefined frame and adjust the font size. This generally will not work terribly well, as you will quickly hit sizes FAR too small to read, even on a retina display. You should adjust the frame of your label to accommodate the excess text (where possible. Sometimes, truncation is the only option.)
Now that, that is out of the way, lets take a look at adjusting the font size. Despite not recommending it, I will still explain how best to go about it.
Important, this code is untested, and will more than likely require some tweaks, but that can be an exercise of the reader.
So the first thing we need to know is the height of a single line. Now, we have the height of the label, and the number of lines it can display, so we can determine this by simply dividing the label height by the number of lines.
CGFloat optimalLineHeight = CGRectGetHeight(label.frame) / label.numberOfLines;
You may have noticed that this may return lines taller than are actually needed. You will be able to implement additional checks and constraints to deal with this. At current though, the font size will also be able to grow, and not just shrink.
Now, getting the optimal line height is just part of the story. We now need to optimise the font size. Here's some code:
CGFloat optimumFontSize = 0.0;
BOOL optimumFontSizeFound = NO;
do {
CGSize charSize = [#"M" sizeWithFont:[UIFont systemFontOfSize:optimumFontSize]
constrainedToSize:CGSizeMake(100, 9999)
lineBreakMode:0];
if ( CGSizeGetHeight(charSize) > optimalLineHeight ) {
optimumFontSizeFound = YES;
}
else {
optimumFontSize++;
}
} while ( !optimumFontSizeFound );
So what does this do? In this we keep track of the optimumFontSize so far. We start with the assumption of a font size of 0, and we see how tall a single character using that font size is. If that height is greater than the optimal line height previously calculated, then the previous height is the optimal one. If not, we increase the size and repeat until we do find the optimal one.
There are still a lot of issues to overcome in this to make it work perfectly in all situations. This should ensure that you don't get visible vertical clipping of characters, but it can't ensure that all the text content will display in the frame. To do that you'll need to be more intelligent in how you determine the number of lines required, but again I'll leave that as an exercise of the reader.
Hope this helps you towards your goal.

Resources