I am using webservice to display content in my ios application.
On web the data is stored using rich edit text box.
so in response I am getting HTML text to display.
Now I had used UITextView to display html text in my application.
[_txt_desc setValue:#"html text here" forKey:#"contentToHTMLString"];
[_txt_desc setFont:[UIFont fontWithName:custom_font_name size:is_ipad?18:14]];
_txt_desc.editable = NO;
_txt_desc.scrollEnabled = NO;
Now I need to calculate size of the uitextview so I can reposition the views below the UItextview.
For calculating height of the uitextview I had used function described below.
+ (CGFloat)measureHeightOfUITextView:(UITextView *)textView withHtmlString:(NSString *)htmlString
{
if (floor(NSFoundationVersionNumber) > NSFoundationVersionNumber_iOS_6_1)
{
// This is the code for iOS 7. contentSize no longer returns the correct value, so
// we have to calculate it.
//
// This is partly borrowed from HPGrowingTextView, but I've replaced the
// magic fudge factors with the calculated values (having worked out where
// they came from)
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
[paragraphStyle setLineBreakMode:NSLineBreakByWordWrapping];
CGFloat measuredHeight=0;
if ([htmlString length]>0) {
textView.translatesAutoresizingMaskIntoConstraints = NO;
// NSAttributedString *attributedString = [[NSAttributedString alloc] initWithData: [htmlString dataUsingEncoding:NSUnicodeStringEncoding] options:#{ NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType ,NSParagraphStyleAttributeName : paragraphStyle,NSFontAttributeName:textView.font} documentAttributes:nil error:nil];
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithData:[htmlString dataUsingEncoding:NSUnicodeStringEncoding] options:#{ NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType ,NSFontAttributeName:[UIFont fontWithName:custom_font_name size:is_ipad?16:14]} documentAttributes:nil error:nil];
textView.attributedText = attributedString;
// Take account of the padding added around the text.
}
CGRect frame = textView.bounds;
NSLog(#"content size %#",NSStringFromCGSize(textView.frame.size));
UIEdgeInsets textContainerInsets = textView.textContainerInset;
UIEdgeInsets contentInsets = textView.contentInset;
CGFloat leftRightPadding = textContainerInsets.left + textContainerInsets.right + textView.textContainer.lineFragmentPadding * 2 + contentInsets.left + contentInsets.right;
CGFloat topBottomPadding = textContainerInsets.top + textContainerInsets.bottom + contentInsets.top + contentInsets.bottom;
frame.size.width -= leftRightPadding;
frame.size.height -= topBottomPadding;
NSString *textToMeasure = textView.text;
if ([textToMeasure hasSuffix:#"\n"])
{
textToMeasure = [NSString stringWithFormat:#"%#-", textView.text];
}
// NSString class method: boundingRectWithSize:options:attributes:context is
// available only on ios7.0 sdk.
NSDictionary *attributes = #{ NSFontAttributeName: textView.font, NSParagraphStyleAttributeName : paragraphStyle,NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType };
CGRect size = [textToMeasure boundingRectWithSize:CGSizeMake(CGRectGetWidth(frame), MAXFLOAT)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:attributes
context:nil];
measuredHeight = ceilf(CGRectGetHeight(size) + topBottomPadding);
return measuredHeight;
}
else
{
CGSize constraintSize = CGSizeMake(textView.frame.size.width, MAXFLOAT);
CGSize labelSize = [textView.text sizeWithFont:textView.font constrainedToSize:constraintSize lineBreakMode:NSLineBreakByWordWrapping];
int occurance = 0;
occurance=(int)[[htmlString componentsSeparatedByString:#"<p>"] count];
float height=0;
if(occurance>1){
height =occurance*(is_ipad?20:10);
}
occurance = (int)[[htmlString componentsSeparatedByString:#"<br/>"] count];
if(occurance>1){
height +=occurance*(is_ipad?20:10);
}
occurance = (int)[[htmlString componentsSeparatedByString:#"<br>"] count];
if(occurance>1){
height +=occurance*(is_ipad?20:10);
}
occurance = (int)[[htmlString componentsSeparatedByString:#"<h2>"] count];
if(occurance>1){
height +=occurance*(is_ipad?30:20);
}
occurance = (int)[[htmlString componentsSeparatedByString:#"<h3>"] count];
if(occurance>1){
height +=occurance*(is_ipad?30:20);
}
return labelSize.height+(is_ipad?30:20)+textView.font.pointSize+height;
}
}
Now it is returning the height properly.
But now issue is when it is displaying text in below version of ios 7.0 its text size is according to given programmatically while in ios 7 its not displaying text as given font size.
So what can be the solution for fontsize to display in ios 7.
Thanks in advance
bskania
Its simple. I hope this will help some one who is struggling with the Uitextview dynamic height issue.
[_txt_desc setValue:#"html text here" forKey:#"contentToHTMLString"];
[_txt_desc sizeThatFits:CGSizeMake(_txt_desc.frame.size.width,FLT_MAX)].height;
thanks
bskania.
Related
I am just developing and application just like news feeding. Some of the cell view I have to set NSAttributedString to textview and get the exact height of textview.
In my NSAttributedString there is HTML content. I have to set in textview because its takes too much time in web view.
The problem is that some of the time I get the perfect height of of textview and some of the time I'm not getting the height of textview. Because ofNSAttributedString some time it considers font height and some time it is not considering it a font height.
You can get more idea if you see my code about what I have done. templbl2 is a UITextview temptext2 is UIView.
Here is my code:
- (void)viewDidLoad
{
[super viewDidLoad];
NSString *str3 = [NSString stringWithFormat:#"<span style=\"font-family: Frank-regular; font-size: 13\">%#</span>", strTerms];
NSAttributedString * attrStr2 = [[NSAttributedString alloc] initWithData:[str3 dataUsingEncoding:NSUTF8StringEncoding] options:#{ NSDocumentTypeDocumentAttribute: NSHTMLTextDocumentType, NSFontAttributeName : [UIFont fontWithName:#"Frank-regular" size:13.0]} documentAttributes:nil error:nil];
templbl2.attributedText = attrStr2;
[templbl2 sizeToFit];
[self textViewHeightForAttributedText:attrStr2];
}
- (CGFloat)textViewHeightForAttributedText:(NSAttributedString *)text
{
CGFloat width = [UIScreen mainScreen].bounds.size.width; // whatever your desired width is
CGRect paragraphRect =
[text boundingRectWithSize:CGSizeMake(300.f, CGFLOAT_MAX)
options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
context:nil];
temptext2.frame = CGRectMake(0, 0, width, paragraphRect.size.height+60);
return paragraphRect.size.height;
}
Try below method:
- (CGFloat)textViewHeightForAttributedText:(NSAttributedString *)text
{
CGFloat width = [UIScreen mainScreen].bounds.size.width; // whatever your desired width is
UITextView *txtView;
txtView.attributedText = attrStr2;
CGSize size = [tvDummyForHeightTemp sizeThatFits:CGSizeMake(width, FLT_MAX)];
temptext2.frame = CGRectMake(0, 0, width, size.height+60);
return size.height;
}
Hope this will help:)
I try to develop autoshrink functionality. I have attributed text, set into a fix-size UILabel. After that I lower the font size and check if the text fits to given container size.
Problem is that UILabel ignores NSLineBreakByWordWrapping if a word is longer than container width. Resulting in I have cut tail word.
Here is the code:
- (void) setCardText:(NSString *)txt {
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:txt];
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
[paragraphStyle setLineBreakMode:NSLineBreakByWordWrapping];
[paragraphStyle setAlignment:NSTextAlignmentCenter];
[attributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0, [txt length])];
self.cardLabel.attributedText = attributedString;
for (CGFloat fontSize = 40; fontSize >=5; fontSize--) {
[self.cardLabel setFont:[UIFont fontWithName:#"GothamPro-Light" size:fontSize]];
[paragraphStyle setLineSpacing:fontSize*0.3f];
[attributedString addAttribute:NSParagraphStyleAttributeName value:paragraphStyle range:NSMakeRange(0, [txt length])];
self.cardLabel.lineBreakMode = NSLineBreakByWordWrapping;
[self.cardLabel sizeToFit];
if (self.cardLabel.frame.size.width <= 220) {
[self.cardLabel setFrame:CGRectMake(40, 40, 220, self.cardLabel.frame.size.height)];
}
if (self.cardLabel.frame.size.height <= 210) {
[self.cardLabel setFrame:CGRectMake(40, 40, self.cardLabel.frame.size.width, 210)];
}
if (self.cardLabel.frame.size.width <= 220 && self.cardLabel.frame.size.height <= 210) {
[self.cardLabel setFrame:CGRectMake(40, 40, 220, 210)];
break;
}
};
And here is the result (I'm sorry for screenshot in Russian): http://take.ms/kg2mG
In the third line the word is cut and its ending is moved to the next line.
I guess it happens because initially this word doesn't fit to container width and was forcibly broken in half. I suppose I need sort of cut-word-detector, which tells me to keep lowering font size. Or another guess is to oblige the UILabel to be expanded by “unfit word”. But I can't find anything that does this job.
Also I can explode given string into words and check if every single of them fits to container width. But I think this method is a wheel inventing. Is there something I missed that can easily solve my issue?
The method sizeToFit calls sizeThatFits: which returns the ‘best’ size to fit the current bounds and then resize label. So at first you constrain the label and it has to fit the given width. You can see description of NSLineBreakByWordWrapping - Wrapping occurs at word boundaries, unless the word itself doesn’t fit on a single line.
For your purposes you should allow label to fit the knowingly more wider width than it requires. But it is difficult because the task is to find the best font size and we cannot predict the width. And the best way is to find font size based on the longest word in the text.
So the algorithm:
Detect the longest word, by separating by spaces.
Iteratively, decrease the font size and calculate the size of the longest word while the word is bigger than required width.
Set calculated font to full text and call sizeThatFits.
Please, find the sample code below ("Verdana" font was used for testing)
- (void) setText {
NSString * text = #"Incidental, indirect, secondary, side rival - Побочный, косвенный, второстепенный, боковой соперник";
CGFloat maxWidth = 300.;
[self setText:text toLabel:self.label maxWidth:maxWidth];
}
- (void) setText:(NSString *)text
toLabel:(UILabel*)label
maxWidth:(CGFloat)maxWidth
{
CGFloat fontSize = [self fontSizeOfWord:[self theLongestWord:text]
initialFontSize:40.
constrainedByWidth:maxWidth];
NSMutableAttributedString * attributedString = [self attributedStringForText:text];
[self setupAttributedStirng:attributedString withFontWithSize:fontSize];
label.attributedText = attributedString;
CGRect labelFrame = label.frame;
labelFrame.size = [label sizeThatFits:[attributedString sizeAdaptedForWidth:maxWidth]];
label.frame = labelFrame;
}
- (NSString*) theLongestWord:(NSString*)text {
NSArray * words = [text componentsSeparatedByString:#" "];
NSUInteger longestLength = 0;
NSUInteger index = NSNotFound;
for(int i = 0; i < words.count; i++) {
NSString * word = words[i];
CGFloat length = word.length;
if(length > longestLength) {
longestLength = length;
index = i;
}
}
return (index != NSNotFound ? words[index] : nil);
}
- (CGFloat)fontSizeOfWord:(NSString *)word
initialFontSize:(CGFloat)initialFontSize
constrainedByWidth:(CGFloat)maxWidth
{
NSMutableAttributedString * wordString = [self attributedStringForText:word];
CGFloat fontSize = initialFontSize;
for (; fontSize >= 5.; --fontSize) {
[self setupAttributedStirng:wordString
withFontWithSize:fontSize];
CGSize wordSize = [wordString sizeAdaptedForWidth:CGFLOAT_MAX];
if(wordSize.width <= maxWidth){
break;
}
}
return fontSize;
}
- (NSMutableAttributedString*) attributedStringForText:(NSString*)text {
return (text&&text.length ? [[NSMutableAttributedString alloc] initWithString:text]:nil);
}
- (void)setupAttributedStirng:(NSMutableAttributedString *)attributedString
withFontWithSize:(CGFloat)fontSize
{
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
[paragraphStyle setLineBreakMode:NSLineBreakByWordWrapping];
[paragraphStyle setAlignment:NSTextAlignmentCenter];
UIFont * font = [UIFont fontWithName:#"Verdana" size:fontSize];
[paragraphStyle setLineSpacing:fontSize*0.3f];
NSDictionary * attributes = #{NSParagraphStyleAttributeName: paragraphStyle,
NSFontAttributeName: font};
[attributedString addAttributes:attributes
range:NSMakeRange(0, [attributedString length])];
}
Category for NSAttributedString:
#implementation NSAttributedString (AdaptedSize)
- (CGSize) sizeAdaptedForWidth:(CGFloat)width
{
CTFramesetterRef framesetter = CTFramesetterCreateWithAttributedString((__bridge CFAttributedStringRef)self);
CGSize targetSize = CGSizeMake(width, CGFLOAT_MAX);
CGSize fitSize = CTFramesetterSuggestFrameSizeWithConstraints(framesetter,
CFRangeMake(0, [self length]),
NULL, targetSize, NULL);
CFRelease(framesetter);
return fitSize;
}
#end
Have you tried the UILabel.adjustsFontSizeToWidth property?
I have an app with a table view that needs to support dynamic cell heights. In the cell's layoutSubviews method, I am generating a frame for my UILabel controls, which are the only dynamic sized controls in the cells.
For some reason, the width being returned from the following method is less than what it should be and the text gets truncated, but only on short text, such as one word. The width should be maintained as the width that is passed in as the initial frame.
That said, what my method needs to accomplish is adjusting the size of the label to fit all the text while maintaining a preset width.
Here's the code I am using:
- (CGRect)getLabelSizeForText:(NSString*)text withInitialRect:(CGRect)labelFrame andFontSize:(CGFloat)fontSize{
CGSize constrainedSize = CGSizeMake(labelFrame.size.width, MAXFLOAT);
NSDictionary *attributesDict = [NSDictionary dictionaryWithObjectsAndKeys:[UIFont systemFontOfSize:fontSize], NSFontAttributeName, nil];
CGSize requiredSize = [text boundingRectWithSize:constrainedSize
options:NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading
attributes:attributesDict context:nil].size;
CGRect adjustedFrameRect = CGRectMake(labelFrame.origin.x, labelFrame.origin.y, requiredSize.width, requiredSize.height);
return adjustedFrameRect;
}
This works for me,
+ (CGSize)textSizeForText:(NSString *)text {
CGRect screenRect = [[UIScreen mainScreen] bounds];
CGFloat screenWidth = screenRect.size.width;
CGFloat width = screenWidth - kPaddingRight;
CGSize maxSize = CGSizeMake(width, CGFLOAT_MAX);
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc] initWithString:text];
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.lineSpacing = 2.5;
NSDictionary *dict = #{NSParagraphStyleAttributeName : paragraphStyle, NSFontAttributeName: [UIFont systemFontOfSize:15] };
[attributedString addAttributes:dict range:NSMakeRange(0, text.length)];
CGRect paragraphRect = [attributedString boundingRectWithSize:maxSize options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading) context:nil];
return paragraphRect.size;
}
My code to calculate height required to for label is as follows:
-(float)frameForText:(NSString*)text sizeWithFont:(UIFont*)font constrainedToSize: (float)width{
NSDictionary *attributesDictionary = [NSDictionary dictionaryWithObjectsAndKeys:font, NSFontAttributeName, nil];
CGRect frame = [text boundingRectWithSize:(CGSize){width, CGFLOAT_MAX} options: (NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading) attributes:attributesDictionary
context:nil];
// This contains both height and width, but we really care about height.
return frame.size.height;
}
I have called it as follows from below code to calculate height firs then used it to draw label
//form attributed title
NSString *str_title =#"This is sample title to calculate height";
NSMutableParagraphStyle *paragraphStyle = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
[paragraphStyle setLineSpacing: 2.0f];
NSDictionary *attributes = #{ NSFontAttributeName: [UIFont fontWithName:#"PTSans-Bold" size:10], NSParagraphStyleAttributeName: paragraphStyle };
NSAttributedString *attributed_title = [[NSAttributedString alloc] initWithString:str_title attributes:attributes];
//calculate height required for title
float comment_height = [self frameForText:str_title sizeWithFont:[UIFont fontWithName:#"PTSans-Bold" size:10] constrainedToSize:250];
UILabel *lbl_title;
//use calculated height here
lbl_title = [[UILabel alloc] initWithFrame:CGRectMake(60, 5, 250, title_height)];
lbl_title.numberOfLines = 0;
lbl_title.attributedText = attributed_title;
This works fine when font is "PTSans-Regular" and gives exact uilabel height. But, it is not working for "PTSans-Bold" by above code.
How should I return exact UIlabel needed to write "PTSans-Bold" text with label of width 250, font's size 10 and paragraph line spacing equal to 2? Note: the "PTSans-Bold" is not system font, it's font I have added.
Thanks.
This is easyiest way to find UILabel text height dynamically height for below iOS7
CGSize fontSize = [uilabel.text sizeWithFont:uilabel.font];
NSLog(#"height %f",fontSize.height);
for iOS7
float heightIs =[uilabel.text boundingRectWithSize:uilabel.frame.size options:NSStringDrawingUsesLineFragmentOrigin attributes:#{ NSFontAttributeName:uilabel.font } context:nil].size.height;
use the below method after setting font and every properties.
- (CGFloat)getHeight:(UILabel *)label{
CGSize sizeOfText = [label.text boundingRectWithSize: CGSizeMake( self.bounds.size.width,CGFLOAT_MAX)
options: (NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
attributes: [NSDictionary dictionaryWithObject:label.font
forKey:NSFontAttributeName]
context: nil].size;
return sizeOfText.height;
}
OK, I solved this problem with below code:
-(float)heightOfAttrbuitedText:(NSAttributedString *)attrStr width:(CGFloat )width{
CGRect rect = [attrStr boundingRectWithSize:CGSizeMake(width, 10000) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading context:nil];
return rect.size.height;
}
Edit: The linked "duplicate" question only deals with calculating text rectangle. I need to calculate actual font size after label scaled it, NOT the string size.
This method is now deprecated:
size = [self sizeWithFont:font // 20
minFontSize:minFontSize // 14
actualFontSize:&actualFontSize // 16
forWidth:maxWidth
lineBreakMode:self.lineBreakMode];
How can I calculate font size of a UILabel now in iOS 7 when it shrunk the size of the text to fit in?
I have the same problem, I need to know the actual size to make that the others UILabels in my UIView match.
I know that it's not a perfect solution, but perhaps it's useful for you.
My solution is: instead of use adjustsFontSizeToFitWidth I calculate "manually" the size.
CGSize initialSize = [_label.text sizeWithAttributes:#{NSFontAttributeName:_label.font}];
while ( initialSize.width > _label.frame.size.width ) {
[_label setFont:[_label.font fontWithSize:_label.font.pointSize - 1]];
initialSize = [_label.text sizeWithAttributes:#{NSFontAttributeName:_label.font}];
}
CGFloat actualSize = _label.font.pointSize;
Distilled from Julius Bahr's answer on this page, this method works perfectly for getting the actual font size after it has been automatically adjusted:
- (CGFloat)getActualFontSizeForLabel:(UILabel *)label
{
NSStringDrawingContext *labelContext = [NSStringDrawingContext new];
labelContext.minimumScaleFactor = label.minimumScaleFactor;
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;
}
I am using this in my application to get the font sizes for three different labels for which I need to keep the sizes in synch while still allowing them to auto-shrink for localized translations that can be quite a bit longer than their original English counterparts.
I call that method once for each label, and then if they are not all the same value, I set the label's font sizes to the minimum of the three.
The use of minFontSize was deprecated on UILabel in iOS 6, and on the NSString drawing additions in iOS 7. If you want to use it and find the actual font size used, you need to use the deprecated method you mentioned in your question.
The replacement for minFontSize is minimumScaleFactor. If you want to find the actual scale factor used, you need to create an NSStringDrawingContext and pass it in the boundingRectWithSize:options:attributes:context: message, like this:
NSStringDrawingContext *context = [[NSStringDrawingContext alloc] init];
context.minimumScaleFactor = 0.7;
[label.text boundingRectWithSize:CGSizeMake(maxWidth, HUGE_VAL)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:#{
NSFontAttributeName: font
} context:context];
CGFloat actualFontSize = font.pointSize * context.actualScaleFactor;
Expanding on Ferran's answer
To expand to fill width or height, whichever it hits first
Swift version
func getFontSizeToFitFrameOfLabel(label: UILabel) -> CGFloat
{
var initialSize : CGSize = label.text!.sizeWithAttributes([NSFontAttributeName : label.font])
if initialSize.width > label.frame.size.width ||
initialSize.height > label.frame.size.height
{
while initialSize.width > label.frame.size.width ||
initialSize.height > label.frame.size.height
{
label.font = label.font.fontWithSize(label.font.pointSize - 1)
initialSize = label.text!.sizeWithAttributes([NSFontAttributeName : label.font])
}
} else {
while initialSize.width < label.frame.size.width &&
initialSize.height < label.frame.size.height
{
label.font = label.font.fontWithSize(label.font.pointSize + 1)
initialSize = label.text!.sizeWithAttributes([NSFontAttributeName : label.font])
}
// went 1 point too large so compensate here
label.font = label.font.fontWithSize(label.font.pointSize - 1)
}
return label.font.pointSize;
}
Then do something like this to use it (say your label is named title1Label)
title1Label.frame = CGRect(x: 0.0, y: 0.0, width: view.frame.size.width, height: view.frame.size.height)
// sets font to some nonzero size to begin with, it will change up or down to fit the label's frame
title1Label.font = UIFont(name: "Super Mario 256", size: 45.0)
title1Label.font = title1Label.font.fontWithSize(getFontSizeToFitFrameOfLabel(title1Label))
// resize height to be a little larger than the font height
title1Label.frame.size.height = title1Label.font.pointSize*1.3
Objective C version:
- (CGFloat) maxFontSize:(UILabel *)label{
CGSize initialSize = [label.text sizeWithAttributes:#{NSFontAttributeName:label.font}];
if (initialSize.width > label.frame.size.width ||
initialSize.height > label.frame.size.height)
{
while (initialSize.width > label.frame.size.width ||
initialSize.height > label.frame.size.height)
{
[label setFont:[label.font fontWithSize:label.font.pointSize - 1]];
initialSize = [label.text sizeWithAttributes:#{NSFontAttributeName:label.font}];
}
} else {
while (initialSize.width < label.frame.size.width &&
initialSize.height < label.frame.size.height)
{
[label setFont:[label.font fontWithSize:label.font.pointSize + 1]];
initialSize = [label.text sizeWithAttributes:#{NSFontAttributeName:label.font}];
}
// went 1 point too large so compensate here
[label setFont:[label.font fontWithSize:label.font.pointSize - 1]];
}
return label.font.pointSize;
}
My specific quest has been to size the font on 2 labels equally with adjustsFontSizeToFitWidth enabled.
The solution works on iOS 6 and 7.
+ (void)sizeLabelFontToMinSizeFor:(UILabel *)label1 and:(UILabel *)label2 {
NSStringDrawingContext *labelContext = [NSStringDrawingContext new];
labelContext.minimumScaleFactor = label1.minimumScaleFactor;
NSAttributedString *attributedString1 = [[NSAttributedString alloc] initWithString:label1.text attributes:#{NSFontAttributeName : label1.font}];
// the NSStringDrawingUsesLineFragmentOrigin and NSStringDrawingUsesFontLeading options are magic
[attributedString1 boundingRectWithSize:label1.frame.size options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading context:labelContext];
CGFloat actualFontSize1 = label1.font.pointSize * labelContext.actualScaleFactor;
labelContext = [NSStringDrawingContext new];
labelContext.minimumScaleFactor = label2.minimumScaleFactor;
NSAttributedString *attributedString2 = [[NSAttributedString alloc] initWithString:label2.text attributes:#{NSFontAttributeName : label2.font}];
[attributedString2 boundingRectWithSize:label2.frame.size options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading context:labelContext];
CGFloat actualFontSize2 = label2.font.pointSize * labelContext.actualScaleFactor;
CGFloat minSize = MIN(actualFontSize1, actualFontSize2);
label1.font = [UIFont fontWithName:RCDefaultFontName size:minSize];
label2.font = [UIFont fontWithName:RCDefaultFontName size:minSize];
}
Next code doesn't support minFontSize and lineBreakMode so if you need them you should improve it by yourself:
CGSize NSString_sizeWithFont(NSString * str, UIFont *font) {
CGSize result;
if (NO == [str respondsToSelector: #selector(sizeWithAttributes:)]) {
// legacy way
result = [str sizeWithFont: font];
} else {
// modern way
NSDictionary * dict = [NSDictionary dictionaryWithObjectsAndKeys:
font, NSFontAttributeName, nil];
result = [str sizeWithAttributes: dict];
}
return result;
}
UIFont * NSString_calcActualFont(NSString * str, UIFont * initialFont,
CGSize sizeLimit, CGSize * actualSize)
{
const CGSize curSize = NSString_sizeWithFont(str, initialFont);
CGFloat actualFontSize = initialFont.pointSize;
actualFontSize *= MIN(sizeLimit.width / curSize.width, sizeLimit.height / curSize.height);
UIFont * actualFont = [initialFont fontWithSize: floorf(actualFontSize)];
*actualSize = NSString_sizeWithFont(str, actualFont);
return actualFont;
}
Simple solution for one-line UILabel:
//myLabel - initial label
UILabel *fullSizeLabel = [UILabel new];
fullSizeLabel.font = myLabel.font;
fullSizeLabel.text = myLabel.text;
[fullSizeLabel sizeToFit];
CGFloat actualFontSize = myLabel.font.pointSize * (myLabel.bounds.size.width / fullSizeLabel.bounds.size.width);
//correct, if new font size bigger than initial
actualFontSize = actualFontSize < myLabel.font.pointSize ? actualFontSize : myLabel.font.pointSize;
Erik van der Neut's code worked for me, so I translated it in Swift and wrapped it in a UILabel extension:
extension UILabel {
public func actualFontSize()-> CGFloat {
let context = NSStringDrawingContext()
context.minimumScaleFactor = self.minimumScaleFactor
let attributedString = NSAttributedString(string: self.text ?? "", attributes: [NSFontAttributeName: self.font])
attributedString.boundingRectWithSize(self.frame.size, options: [.UsesLineFragmentOrigin], context: context)
return (self.font.pointSize * context.actualScaleFactor)
}
}