How to make NSStringDrawingContext shrink text? - ios

I'm trying to use the attributed string API of iOS 6 to calculate the size of text and shrink the font size if necessary. However, I can't get it to work as the documentation says.
NSString *string = #"This is a long text that doesn't shrink as it should";
NSStringDrawingContext *context = [NSStringDrawingContext new];
context.minimumScaleFactor = 0.5;
UIFont *font = [UIFont fontWithName:#"SourceSansPro-Bold" size:32.f];
NSMutableParagraphStyle *paragraphStyle = [NSMutableParagraphStyle new];
paragraphStyle.lineBreakMode = NSLineBreakByClipping;
NSDictionary *attributes = #{ NSFontAttributeName: font,
NSParagraphStyleAttributeName: paragraphStyle };
NSAttributedString *attributedString = [[NSAttributedString alloc] initWithString:self.title attributes:attributes];
CGRect rect = [attributedString boundingRectWithSize:CGSizeMake(512.f, 512.f) options:NSStringDrawingUsesLineFragmentOrigin context:context];
NSLog(#"rect: %#, context: %#", NSStringFromCGRect(rect), context.debugDescription);
But the text doesn't shrink and is truncated. actualScaleFactor is always 1. The log results are:
rect:{{0, 0}, {431.64801, 80.447998}}, context:<NSStringDrawingContext: 0x14e85770> minimumScaleFactor:0.500000 minimumTrackingAdjustment:0.000000 actualScaleFactor:1.000000 actualTrackingAdjustment:0.000000 totalBounds:{{0, 0}, {431.64801, 80.447998}}
The result is the same if I use the actual drawing method and not the measuring method. If I remove the paragraph style, it makes the text wrap and doesn't shrink it. If I remove the paragraph style AND I choose a size that only allows one line of text, the text is truncated too instead of being shrunk. What is wrong? There is very little documentation or online resources dealing with NSStringDrawingContext. And I'm trying to avoid the use of sizeWithFont:minFontSize:actualFontSize:forWidth:lineBreakMode: which is deprecated in iOS 7.

NSStringDrawingContext's minimumScaleFactor appears to be broken in iOS 7.
As far as I can make out, even in iOS 6, it didn't work for drawing; it worked for measuring, so you could work out what would happen in a context where it does work for drawing, like a UILabel. That way, you know the correct minimum height for the label.
Or, you could use the resulting scale factor to shrink the text yourself, in the knowledge that now it will fit.
Example:
- (void)drawRect:(CGRect)rect
{
// rect is 0,0,210,31
NSMutableAttributedString* s =
[[NSMutableAttributedString alloc] initWithString: #"This is the army Mister Jones."];
[s addAttributes:#{NSFontAttributeName:[UIFont fontWithName:#"GillSans" size:20]}
range:NSMakeRange(0,s.length)];
NSMutableParagraphStyle* para = [[NSMutableParagraphStyle alloc] init];
para.lineBreakMode = NSLineBreakByTruncatingTail;
[s addAttributes:#{NSParagraphStyleAttributeName:para}
range:NSMakeRange(0,s.length)];
NSStringDrawingContext* con = [[NSStringDrawingContext alloc] init];
con.minimumScaleFactor = 0.5;
CGRect result =
[s boundingRectWithSize:rect.size
options:NSStringDrawingUsesLineFragmentOrigin context:con];
CGFloat scale = con.actualScaleFactor;
// ...(could have a check here to see if result fits in target rect)...
// fix font to use scale factor, and draw
[s addAttributes:#{NSFontAttributeName:[UIFont fontWithName:#"GillSans" size:20*scale]}
range:NSMakeRange(0,s.length)];
[s drawWithRect:rect options:NSStringDrawingUsesLineFragmentOrigin context:nil];
}
In iOS 6, scale is about 0.85 and you can use it as shown to shrink the text. But in iOS 7, scale remains at 1, suggesting that no shrinkage is happening and this feature of NSStringDrawingContext is now useless. I can't tell whether that's a bug or whether the feature has been deliberately abandoned.

After googling for a long time I did not find a solution working under iOS7. Right now I use the following workaround, knowing that it is very ugly. I render a UILabel in memory, take a screenshot and draw that. UILabel is able to shrink the text correctly.
But perhaps someone finds it useful.
UILabel *myLabel = [[UILabel alloc] initWithFrame:myLabelFrame];
myLabel.font = [UIFont fontWithName:#"HelveticaNeue-BoldItalic" size:16];
myLabel.text = #"Some text that is too long";
myLabel.minimumScaleFactor = 0.5;
myLabel.adjustsFontSizeToFitWidth = YES;
myLabel.backgroundColor = [UIColor clearColor];
UIGraphicsBeginImageContextWithOptions(myLabelFrame.size, NO, 0.0f);
[[myLabel layer] renderInContext:UIGraphicsGetCurrentContext()];
UIImage *screenshot = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[screenshot drawInRect:myLabel.frame];

Just wanted to post this solution as I have been battling with this for a couple hours. I could never get the text area to scale down and would always have the last lines of text cut off.
The solution for me was to not add a context and just set it to nil. I got this when looking at the example on this site. https://littlebitesofcocoa.com/144-drawing-multiline-strings
Note after getting the size the box would draw at using
pageStringSize = [myString boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attrsDictionary context:nil];
I still needed to loop through scaling down the font manually:
while (pageStringSize.size.height > 140 && scale > 0.5) {
scale = scale - 0.1;
font = [UIFont fontWithName:#"Chalkboard SE" size:24.0 *scale];
attrsDictionary = [NSDictionary dictionaryWithObjectsAndKeys:font, NSFontAttributeName,[NSNumber numberWithFloat:1.0], NSBaselineOffsetAttributeName, nil];
pageStringSize = [myString boundingRectWithSize:maxSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attrsDictionary context:nil];
}

Related

Problems with sizes and lines in UILabel

I have a little question. I developed an application that automatically fills a data set obtained from a SQLite database. These data are drawn from dynamic way, and between the data I have a frame where I insert labels dynamically. I never know the exact number of labels inside these frame because I take data from different tables of my Database. The problem I have with the labels which texts within them do not fill within the width of the label. I tried to use label.linebreakmode but still makes the break. I post the code:
There I have many objects taken from previous code, like widthFormato and widthImageFormato
if([tipoVino length]!=0){
UILabel *lblFormato = [[UILabel alloc] init];
labelX = ((widthFormato-widthImageFormatos) / 2)+10;
CGRect labelFrame = lblFormato.frame;
labelFrame.size.width = widthImageFormatos;
labelFrame.origin.x = labelX;
labelFrame.origin.y = labelY+25;
lblFormato.frame = labelFrame;
lblFormato.numberOfLines = 0;
lblFormato.lineBreakMode = NSLineBreakByWordWrapping;
[lblFormato setText:[NSString stringWithFormat:#"- %#",tipoVino]];
lblFormato.textColor = [UIColor whiteColor];
labelY = lblFormato.frame.origin.y;
[formatosImageView addSubview:lblFormato];
}
I think that you want to create labels with flexible height (not all have the same size), and must fix the width of formatosImageView.
EDITED FOR iOS7
I was using sizeWithFont that is deprecated in iOS7, I changed it for boundingRectWithSize
Try this:
UILabel *lblFormato = [[UILabel alloc] init];
lblFormato.numberOfLines = 0;
lblFormato.lineBreakMode = NSLineBreakByWordWrapping;
[lblFormato sizeToFit];
NSString *string = [NSString stringWithFormat:#"- %#", tipoVino];
[lblFormato setText: string];
//Calculate the size of the container of the lblFormato
CGSize size = [string boundingRectWithSize:CGSizeMake(widthImageFormatos, 2000) options:NSStringDrawingUsesLineFragmentOrigin attributes:#{NSFontAttributeName: [UIFont fontWithName:#"customFont" size:fontSize]} context:nil];
lblFormato.frame = CGRectMake(labelX, labelY + 25, size.width, size.height);
and you must update the labelY with:
labelY = lblFormato.frame.size.height;
Maybe it helps you.
With regards #spinillos answer, for iOS 7:
NSDictionary *attributes = #{ NSFontAttributeName : [UIFont systemFontOfSize:<#fontSize#>] };
NSMutableAttributedString *attributedString = [[NSMutableAttributedString alloc]
initWithString: <#string#>,
attributes:attributes];
CGRect frame = [attributedString boundingRectWithSize:CGSizeMake(<#width#>, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin context:nil];

iOS - Change UILabel font size proportionally

I have several UILables in my view like this:
Those in red colour should never change their font size. Two others in white colour (50 and 50) should decrease their font size proportionally to fit the content.
What I want to achieve is when any of white labels becomes two big the other should start decreasing in size as well:
but instead I get this:
How can I make my UILabels' font size to resize proportionally?
The easiest possible way is as below:
Identify the max possible widths of label 1 and label2.
Sum up widths of both label.
Append both the strings which you want to set in both labels.
Get the font size that fits given (combined) text in combined width.
Now calculate the width of first text for above identified font size and assign it to first label.
Adjust rest of the labels frame according to its frame.
Similarly identify the width of second label.
Assign above derived font size to both labels.
One easy way to do this is by adjusting the fontSize manually using UILabel's sizeToFit method to calculate its bounds.
int fontSize = MAX_FONTSIZE;
BOOL fontSizeNeedsToBeAdjusted = NO;
CGRect originalLabelFrame1 = label1.frame;
CGRect originalLabelFrame2 = label2.frame;
while(fontSizeNeedsToBeAdjusted && fontSize>MIN_FONTSIZE){
label1.frame = originalLabelFrame1;
label1.font = [UIFont systemFontOfSize:fontSize];
[label1 sizeToFit];
label2.frame = originalLabelFrame2;
label2.font = [UIFont systemFontOfSize:fontSize];
[label2 sizeToFit];
if(CGRectGetWidth(label1.frame)>CGRectGetWidth(originalLaeblFrame1) || CGRectGetHeight(label1.frame)>CGRectGetHeight(originalLaeblFrame1))
|| CGRectGetWidth(label2.frame)>CGRectGetWidth(originalLaeblFrame2) || CGRectGetHeight(label2.frame)>CGRectGetHeight(originalLaeblFrame2)){
fontSizeNeedsToBeAdjusted = YES;
fontSize--;
}else
fontSizeNeedsToBeAdjusted = NO;
}
A quicker way would be calculating the size with NSString's boundingRectWithSize: options:attributes:context: method:
NSDictionary *attributes = #{NSFontAttributeName: [UIFont systemFontofSize:fontSize]};
// NSString class method: boundingRectWithSize:options:attributes:context is
// available only on ios7.0 sdk.
CGRect bounds = [label1.text boundingRectWithSize:CGSizeMake(CGRectGetWidth(label1.frame), MAXFLOAT)
options:NSStringDrawingUsesLineFragmentOrigin
attributes:attributes
context:nil];
I expected the answer to this is in NSMutableAttributedString so i did a small experiment with this
NSString *quantity = #"123456";
NSString *metrics =#"m2";
NSString *quantity2 = #"50";
NSString *metrics2 =#"pk";
NSString *separator = #" / ";
NSMutableAttributedString *fullString = [[NSMutableAttributedString alloc] initWithString:
[NSString stringWithFormat:#"%#%#%#%#%#",quantity,metrics,separator,quantity2,metrics2]];
[fullString addAttribute:NSFontAttributeName
value:[UIFont systemFontOfSize:25.0]
range:NSMakeRange(quantity.length+metrics.length, separator.length)];
[fullString addAttribute:NSFontAttributeName
value:[UIFont systemFontOfSize:12.0]
range:NSMakeRange(fullString.length-metrics.length, metrics.length)];
_lblTitle.attributedText = fullString;
So try changing the values and adding attributes you will get what you want

UILabel shows fewer lines then expected with multiple lines mode

I'd like my UILabel to show my data in multiple lines, and the data is fetch from txt file.
I've searched online for this question, and all the answers show that I just need to set lineBreakMode to NSLineBreakByWordWrapping and numberOfLines = 0. However, the problem is that, even though I added these settings, the labels shows fewer lines than expected (my data has 4 lines, however the label only shows 2 lines). Here's my code:
-(void)updateFileContentLabel:(NSString*)content{
self.fileContentLabel.text = content;
self.fileContentLabel.lineBreakMode = NSLineBreakByWordWrapping;
self.fileContentLabel.font = [UIFont systemFontOfSize:17.0f];
self.fileContentLabel.numberOfLines = 0;
[self.fileContentLabel sizeToFit];
NSLog(#"file Label: %#",self.fileContentLabel.text);
}
As you see, I have a NSLog to show the content of label's text. And the text of label in NSLog is correct (4 lines). However on the phone or simulator, it only shows 2 lines instead of 4. Does anyone know where is the problem? Thanks!
Probably your UILabel is just not big enough. Solutions:
1.Increase a label size.
2.Add following code to allow UILabel to decrease the font size if needed.
self.fileContentLabel.minimumScaleFactor = 0.5;
I was only able to replicate your problem when I was using auto layout and didn't have constraints set up for fileContentLabel, so check that out.
Use the following method to calculate the height of text and set the frame of label accordingly.
- (CGSize)boundingSizeWithFont:(UIFont *)font constrainedToSize:(CGSize)size lineBreakMode:(NSLineBreakMode)lineBreakMode
{
CGSize stringSize;
if ([self respondsToSelector:#selector(boundingRectWithSize:options:attributes:context:)]) {
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
paragraphStyle.lineBreakMode = lineBreakMode;
NSDictionary * stringAttributes = #{ NSFontAttributeName: font, paragraphStyle: NSParagraphStyleAttributeName};
stringSize = [self boundingRectWithSize:size
options:NSStringDrawingUsesLineFragmentOrigin
attributes:stringAttributes context:nil].size;
stringSize = CGSizeMake(ceil(stringSize.width), ceil(stringSize.height) + 2);
} else {
stringSize = [self sizeWithFont:font constrainedToSize:size lineBreakMode:lineBreakMode];
}
return stringSize;
}

drawInRect:withAttributes vs drawInRect:withFont:lineBreakMode:alignment

I'm working on a new version of my app and am attempting to replace deprecated messages, but am not able to get past this one.
I can't figure out why drawInRect:withAttributes is not working. The code displays properly when drawInRect:withFont:lineBreakMode:alignment message is sent, but does not work when drawInRect:withAttributes is sent.
I'm using the same rect and font and I what I believe is the same text style. The constants are just positioning the rect just below an image, but I'm using the same rect for both calls, so I'm certain the rectangle is correct.
(note that bs.name used below is an NSString object)
CGRect textRect = CGRectMake(fCol*kRVCiPadAlbumColumnWidth,
kRVCiPadAlbumColumnWidth-kRVCiPadTextLabelYOffset,
kRVCiPadAlbumColumnWidth,
kRVCiPadTextLabelHeight);
NSMutableParagraphStyle *textStyle = [[NSMutableParagraphStyle defaultParagraphStyle] mutableCopy];
textStyle.lineBreakMode = NSLineBreakByWordWrapping;
textStyle.alignment = NSTextAlignmentCenter;
UIFont *textFont = [UIFont systemFontOfSize:16];
This doesn't work (nothing is drawn on the screen) using the variables from above
[bs.name drawInRect:textRect
withAttributes:#{NSFontAttributeName:textFont,
NSParagraphStyleAttributeName:textStyle}];
This Does work (the string is drawn properly on the screen) using the same variables from above
[bs.name drawInRect:textRect
withFont:textFont
lineBreakMode:NSLineBreakByWordWrapping
alignment:NSTextAlignmentCenter];
Any assistance would be great. Thanks.
To set the color of text you need to pass the NSForegroundColorAttributeName in the attribute as the additional parameter.
NSDictionary *dictionary = #{ NSFontAttributeName: self.font,
NSParagraphStyleAttributeName: paragraphStyle,
NSForegroundColorAttributeName: self.textColor};
I've made a UIView with drawRect: containing only the code you provided
- (void)drawRect:(CGRect)frame
{
NSMutableParagraphStyle *textStyle = [[NSMutableParagraphStyle defaultParagraphStyle] mutableCopy];
textStyle.lineBreakMode = NSLineBreakByWordWrapping;
textStyle.alignment = NSTextAlignmentCenter;
UIFont *textFont = [UIFont systemFontOfSize:16];
NSString *text = #"Lorem ipsum";
// iOS 7 way
[text drawInRect:frame withAttributes:#{NSFontAttributeName:textFont, NSParagraphStyleAttributeName:textStyle}];
// pre iOS 7 way
CGFloat margin = 16;
CGRect bottomFrame = CGRectMake(0, margin, frame.size.width, frame.size.height - margin);
[text drawInRect:bottomFrame withFont:textFont lineBreakMode:NSLineBreakByWordWrapping alignment:NSTextAlignmentCenter];
}
I don't see any difference between the outputs of these two methods. Maybe the problem is somewhere else in your code?

How to obtain adjusted font size from UILabel when adjustsFontSizeToFitWidth is YES in iOS 7?

I have a label that is set to adjustsFontSizeToFitWidth = YES and I need to get the actual displayed font size.
Now iOS 7 deprecated all methods that worked previously and all questions on SO suggest using these deprecated methods.
I will make this question a bounty as soon as I am allowed to by SO. Please do not close.
UILabel displayed fontSize in case of using adjustsFontSizeToFitWidth in iOS 7
UILabel *label=[[UILabel alloc]initWithFrame:CGRectMake(0, 0, 100, 40)];
label.text = #" Your Text goes here into this label";
label.adjustsFontSizeToFitWidth = YES;
NSMutableAttributedString *attrStr = [[NSMutableAttributedString alloc] initWithAttributedString:label.attributedText];
// Get the theoretical font-size
[attrStr setAttributes:#{NSFontAttributeName:label.font} range:NSMakeRange(0, attrStr.length)];
NSStringDrawingContext *context = [NSStringDrawingContext new];
context.minimumScaleFactor = label.minimumScaleFactor;
[attrStr boundingRectWithSize:label.frame.size options:NSStringDrawingUsesLineFragmentOrigin context:context];
CGFloat theoreticalFontSize = label.font.pointSize * context.actualScaleFactor;
NSLog(#"theoreticalFontSize: %f",theoreticalFontSize);
NSLog(#"AttributedString Width: %f", [attrStr size].width);
double scaleFactor=label.frame.size.width/([attrStr size].width);
double displayedFontSize=theoreticalFontSize*scaleFactor;
NSLog(#"Actual displayed Font Size: %f",displayedFontSize);
// Verification of Result
double verification=(displayedFontSize * [attrStr length]);
NSLog(#"Should be equal to %0.5f: %0.5f ", [attrStr size].width/17.0, label.frame.size.width/displayedFontSize);
Try inserting [lblObj sizeToFit] just before requesting the font size
There's a readonly property that lets you do that. You can access it like this
nameLabel.adjustsFontSizeToFitWidth = YES;
//Make sure to use the line below AFTER the line above
float fontSize = nameLabel.font.xHeight;
This will give you the font size after it has been adjusted to fit width.
You can get the font size of UILabel text using these line of code.
UILabel *lblObj = [[UILabel alloc]init];
lblObj.text = #" Your Text";
lblObj.adjustsFontSizeToFitWidth = YES;
float size = lblObj.font.pointSize; //Here You will get the actual size of the text.
float lineHeight = lblObj.font.lineHeight;
Try this one.

Resources