iOS drawing text in a given width - ios

I am looking for a way of finding the correct font size in order to draw text onto a map at the correct width (which changes as the user zooms in and out of the map). I used to use the following code:
+(float) calulateHeightFromMaxWidth:(NSString*)text withMaxWidth:(float)maxWidth withMaxFontSize:(float)maxFontSize{
CGFloat fontSize;
[text sizeWithFont:[UIFont systemFontOfSize:maxFontSize] minFontSize:1 actualFontSize:&fontSize forWidth:maxWidth lineBreakMode:NSLineBreakByTruncatingTail];
return fontSize;
}
This method always returned the correct answer, however sizeWithFont is depicted in iOS 7 and I cannot find a replacement that will return the font size after given it a width. I have found many posts on this site that will give you the width after you have specified a size but I cannot find the opposite (sizeWithAttributes:). I am trying to avoid a solution which involves looping through different font sizes till I find one that fits, as this method could be called 100's maybe 1000's times a draw.

Take a look at [NSString boundingRectWithSize:options:attributes:context:] You can pass MAXFLOAT for both height and width of the parameter size to get the actual size of the text.
EDIT: here's some code that calculates the ideal font size fairly efficiently, using the non-deprecated method:
+(float) calulateHeightFromMaxWidth:(NSString*)text withMaxWidth:(float)maxWidth withMaxFontSize:(float)maxFontSize{
// The less exact you try to match the width, the fewer times the method will need to be called
CGFloat textWidthMatchDelta = 10;
CGFloat fontSize = maxFontSize;
CGFloat minFontSize = 0;
// If drawing a single line of text, omit `|NSStringDrawingUsesLineFragmentOrigin`.
NSUInteger textOptions = NSStringDrawingTruncatesLastVisibleLine|NSStringDrawingUsesLineFragmentOrigin;
while (YES) {
CGRect textRect = [text boundingRectWithSize:CGSizeMake(maxWidth, MAXFLOAT)
options:textOptions
attributes:#{NSFontAttributeName : [UIFont systemFontOfSize:fontSize]
context:nil];
CGFloat textWidth = CGRectGetWidth(textRect);
if (textWidth > maxWidth) {
maxFontSize = fontSize;
fontSize /= 2.0f;
} else if (textWidth + textWidthMatchDelta < maxWidth) {
minFontSize = fontSize;
fontSize = minFontSize + (maxFontSize - minFontSize) / 2.0f;
} else {
break;
}
}
return fontSize;
}

Related

iOS 8 boundingRectWithSize Calculates Wrong Text Size

I have a string thats length will change. I have a textView with a fixed width and height. I want to find the maximum font size that will fit within these bounds. I use boundingRectWithSize. I assume once it gives me a value larger then my testRect I can lower the font and I should be good. But the string is too large for my textView. I always have to lower it by 2-4 points even though the testRect.size.height is lower then my textView.frame.size.height.
NSString *testString = #"This is a test string. Does it get biger?";
CGFloat height = self.textView.frame.size.height;
CGFloat width = self.textView.frame.size.width;
CGSize testSize = CGSizeMake(width, height);
CGFloat fontSize = 15;
NSDictionary *attrsDictionary;
CGRect testRect;
int i = 0;
while (i == 0) {
attrsDictionary = #{NSFontAttributeName: [UIFont fontWithName:#"Helvetica" size:fontSize]};
testRect = [testString boundingRectWithSize:testSize options:NSStringDrawingUsesLineFragmentOrigin attributes:attrsDictionary context:nil];
if (testRect.size.height < testSize.height) {
fontSize++;
} else {
fontSize--;
self.textView.font = [UIFont fontWithName:#"Helvetica" size:fontSize];
self.textView.text = testString;
i = 1;
}
}
When you have a flawless font your approach should work.
Unfortunately flawless fonts are rare, even system fonts usually aren't.
This means you have to find the required adjustments for your font yourself ...

boundingRectWithSize not replicating UITextView

My requirement in a project is that the font size of the UITextView should decrease according the content of the UITextView. So i am trying to do estimate the size of the text using boundingRectWithSize.
The problem is that the font size I get is a bit too big and some part of the text does get clipped.
My Function :
-(BOOL)updateTextViewFontSizeForText:(NSString*)text{
float fontSize = self.maximumFontSizeInPoints;
self.font = [self.font fontWithSize:fontSize];
CGSize tallerSize ;
CGSize stringSize ;
do
{
if (fontSize <= self.minimumFontSizeInPoints) // it just won't fit
return NO;
fontSize -= 1.0;
self.font = [self.font fontWithSize:fontSize];
NSMutableParagraphStyle *paragraphStyle = [[NSMutableParagraphStyle alloc] init];
[paragraphStyle setLineBreakMode:NSLineBreakByWordWrapping];
NSDictionary *attributes = #{ NSFontAttributeName: self.font, NSParagraphStyleAttributeName : paragraphStyle };
tallerSize = CGSizeMake(self.frame.size.width,self.frame.size.height-16);// the 16 is given because uitextview adds some offset
stringSize = [text boundingRectWithSize:CGSizeMake(self.contentSize.width,CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingUsesFontLeading attributes:attributes context:nil].size;
}while(stringSize.height >= tallerSize.height);
if ([self.onTextChangDelegate respondsToSelector:#selector(onTextChangDelegate)]) {
[self.onTextChangDelegate onTextChanged:text];
}
return YES;
}
I ran into the same issue when trying to do the same thing.
The issue is how UITextView run's its line-breaks compared to boundingRectWithSize. You can read more details here: https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/TextLayout/Concepts/CalcTextLayout.html
But you can actually calculate the exact size! There are basically two properties of a UITextView that you'll need to take into account in order to get correct size estimates. The first is textContainer.lineFragmentPadding, the second is textContainerInset.
First, textContainer.lineFragmentPadding: You may have noticed that your sizing is generally always off by 10px, this is because the systems default value is 5px. When you're calculating your estimated size, you'll need to subtract this value from the size you're checking against and add it back when you have your final value.
Second, textContainerInset. This is a UIEdgeInset that you'll need to add back to your final calculated value to match the systems.
This is code based on how I solved the issue:
- (CGSize)sizeThatFits:(CGSize)size
CGFloat lineFragmentPaddings = self.textContainer.lineFragmentPadding * 2;
CGFloat horzPadding = self.textContainerInset.left + self.textContainerInset.right + lineFragmentPaddings;
CGFloat vertPadding = self.textContainerInset.top + self.textContainerInset.bottom;
size.width -= horzPadding;
CGRect boundingRect = [attributedText boundingRectWithSize:size options:NSStringDrawingUsesLineFragmentOrigin context:nil];
size = boundingRect.size;
// I found through debugging that adding 0.25 rounded
// matches sizeThatFits: identically. Not sure why…
size.width += horzPadding + 0.25;
size.height += vertPadding + 0.25;
size = CGSizeRound(size);
return size;
}
Note, CGSizeRound is just a custom function I wrote that rounds the width and height of the CGSize to the nearest 0.5.
For comparison, if you create a second UITextView, and make sure the textContainer.lineFragmentPadding and textContainerInset are the same, you should see the values almost identical to the nearest 0.5.
And to your question about calculating a proper pointSize, this is some pseudo code for that:
CGFloat pointSize = 64;
CGFloat minPointSize = 32;
CGFloat decrementor = 4;
CGFloat padding = self.textContainerInset.left + self.textContainerInset.right + lineFragmentPaddings;
CGFloat actualWidth = self.maxTextViewSize.width - padding * 2;
CGRect boundingRect = CGRectZero;
BOOL isValidPointSize = NO;
do {
if (pointSize < minPointSize) {
pointSize = minPointSize;
boundingRect.size.height = self.maxTextViewSize.height;
isValidPointSize = YES;
} else {
NSDictionary *defaultAttributes = [self.customTextStorage defaultAttributesForPointSize:pointSize];
NSAttributedString *attrString = [[NSAttributedString alloc] initWithString:string attributes:defaultAttributes];
boundingRect = [attrString boundingRectWithSize:CGSizeMake(actualWidth, 1024) options:NSStringDrawingUsesLineFragmentOrigin context:nil];
// is the height to big?
if (boundingRect.size.height > self.maxTextViewSize.height) {
// reduce the point size for next iteration of loop
pointSize -= decrementor;
}
// passes height test
else {
isValidPointSize = YES;
}
}
} while (!isValidPointSize);
return pointSize;
Again, the above is pseudo code based on my implementation (not meant for just drop in replacement for what you have). Hope this helps!
try like this
UITextView *textViewObj;//initialise textview.
textViewObj.autoresizesSubviews = NO;
textViewObj.autoresizingMask = UIViewAutoresizingNone;
This is really working in swift,get original height of textview.. try this
let
size = cellQueue.contentLbl.sizeThatFits(CGSizeMake(cellQueue.contentLbl.frame.size.width,CGFloat(MAXFLOAT))) cellQueue.heightConstraintContentLbl.constant = size.height

Dynamic CCLabelTTF font size in a specified rectangle - cocos2d

I am trying to create a text box that is specified by x, y, w, and h that can accept any string and adjust the font size to be as big as possible and still fit in the box. I have been trying several approaches, but it doesn't quite work. Here is my closest attempt:
+ (CCLabelTTF*) wrap_text :(id)not_self :(NSString *)label :(double)x :(double)y : (double)w :(double)h :(float) fontScale :(ccColor3B) color_front
{
CGSize size;
size = [[CCDirector sharedDirector] winSize];
int boxWidth = (w - x) * size.width;
int boxHeight = (h - y) * size.height;
double middleX = ((w-x)/2 + x);
double middleY = (1 - y);
middleX *= size.width;
middleY *= size.height;
//float font_size = size.height * h * fontScale;
int font_min = 5;
int font_max = 300;
int font_size = 5;
int font_size_final = -1;
int max_interations = 10;
int i;
NSString *hello = label;
UIFont *font = [UIFont fontWithName:GLOBAL_FONT size:font_size];
CGSize realSize = [hello sizeWithFont:font constrainedToSize:CGSizeMake(boxWidth, boxHeight) lineBreakMode:UILineBreakModeWordWrap ];
i = 0;
while ( true ) {
if(realSize.width < boxWidth && realSize.height < boxHeight){
//we found a good value, let's record it and try to go bigger
font_size_final = font_size;
font_min = font_size;
font_size = (font_max - font_min) / 2 + font_min;
}else if(realSize.width > boxWidth || realSize.height > boxHeight){
//too big, let's try a smaller font
font_max = font_size;
font_size = (font_max - font_min) / 2 + font_min;
}
if(font_max == font_min || i >= max_interations)
break;
font = [UIFont fontWithName:GLOBAL_FONT size:font_size];
realSize = [hello sizeWithFont:font constrainedToSize:CGSizeMake(boxWidth, boxHeight) lineBreakMode:UILineBreakModeWordWrap ];
i++;
}
G_label = [CCLabelTTF labelWithString:label fontName:GLOBAL_FONT fontSize:font_size dimensions:CGSizeMake(boxWidth, boxHeight) hAlignment:UITextAlignmentCenter];
G_label.color = color_front;
G_label.anchorPoint = ccp(0.5f, 1);
G_label.position = ccp( middleX , middleY );
[not_self addChild:G_label];
return G_label;
}
If I change boxWidth and boxHeight to 200, I get slightly better results, but I don't understand why and it's still not perfect. Can somebody tell me where I am going astray? Thanks.
*This answer is not mine: original answer is posted here - https://stackoverflow.com/a/9060833/1590951
above post gives credit to http://www.11pixel.com/blog/28/resize-multi-line-text-to-fit-uilabel-on-iphone/
I have only made minor edits it to make it easier to test in cocos2d (tested on ancient cc2d 1.1 so if you have a different version you'll have to make the relevant edits)
You could try the following as a test to see if this will work for your needs:
NSString *fontString = #"Arial";
CGSize targetSize = CGSizeMake(300.0f, 300.0f);
int i;
int fontSizeStep = 1;
int fontSizeMin = 6;
int fontSizeMax = 28;
CCSprite *bg = [CCSprite spriteWithFile:#"Default.png"];
[bg setScaleX: targetSize.width / bg.contentSize.width];
[bg setScaleY: targetSize.height / bg.contentSize.height];
[bg setPosition:ccp(s.width / 2.0f, s.height / 2.0f)];
[bg setColor:ccc3(128, 128, 128)];
[self addChild:bg];
NSString *sampleText = #"Now the way that the book winds up is this: Tom and me found the money that the robbers hid in the cave, and it made us rich. We got six thousand dollars apiece -­ all gold. It was an awful sight of money when it was piled up. Well, Judge Thatcher he took it and put it out at interest, and it fetched us a dollar a day apiece all the year round -­ more than a body could tell what to do with. The Widow Douglas she took me for her son, and allowed she would sivilize me; but it was rough living in the house all the time, considering how dismal regular and decent the widow was in all her ways; and so when I couldn't stand it no longer I lit out. I got into my old rags and my sugar-hogshead again, and was free and satisfied. But Tom Sawyer he hunted me up and said he was going to start a band of robbers, and I might join if I would go back to the widow and be respectable. So I went back.";
// NSString *sampleText = #"Once upon a midnight dreary, while I pondered, weak and weary, Over many a quaint and curious volume of forgotten lore— While I nodded, nearly napping, suddenly there came a tapping, As of some one gently rapping, rapping at my chamber door— \"'Tis some visitor,\" I muttered, \"tapping at my chamber door— Only this and nothing more.";
UIFont *font = [UIFont fontWithName:fontString size:fontSizeMax];
/* Time to calculate the needed font size.
This for loop starts at the largest font size, and decreases by two point sizes (i=i-2)
Until it either hits a size that will fit or hits the minimum size we want to allow (i > 10) */
for(i = fontSizeMax; i > fontSizeMin; i-=fontSizeStep)
{
// Set the new font size.
font = [font fontWithSize:i];
// You can log the size you're trying: NSLog(#"Trying size: %u", i);
/* This step is important: We make a constraint box
using only the fixed WIDTH of the UILabel. The height will
be checked later. */
CGSize constraintSize = CGSizeMake(targetSize.width, MAXFLOAT);
// This step checks how tall the label would be with the desired font.
CGSize labelSize = [sampleText sizeWithFont:font constrainedToSize:constraintSize lineBreakMode:CCLineBreakModeWordWrap];
/* Here is where you use the height requirement!
Set the value in the if statement to the height of your UILabel
If the label fits into your required height, it will break the loop
and use that font size. */
if(labelSize.height <= targetSize.height)
{
NSLog(#"Break on: %u", i);
break;
}
}
if(i == fontSizeMin)
{
NSLog(#"* possibly truncated text *");
}
// You can see what size the function is using by outputting: NSLog(#"Best size is: %u", i);
CCLabelTTF *label = [CCLabelTTF labelWithString:sampleText dimensions:targetSize alignment:CCTextAlignmentLeft fontName:fontString fontSize:i];
[label setPosition:ccp(s.width / 2.0f, s.height / 2.0f)];
[self addChild:label];

Calculate NSString size to adjust UITextField frame

I have issues calculating the accurate size of a NSString displayed in a UITextField.
My goal is to update the textfield frame size according to the string size programmatically (without using sizeToFit). I am using the sizeWithFont function.
-(void)resizeTextFieldAccordingToText:(NSString*)textFieldString {
CGPoint originalCenter = self.textField.center;
UIFont* currentFont = [textField font];
CGSize newSize = [textFieldString sizeWithFont:currentFont];
//Same incorrect results with the extended version of sizeWithFont, e.g.
//[textFieldString sizeWithFont:currentFont constrainedToSize:CGSizeMake(300.0, 100.0) lineBreakMode:NSLineBreakByClipping];
[self.textField setFrame:(CGRectMake(self.textField.frame.origin.x, self.textField.frame.origin.y, newSize.width, newSize.height))];
[self.textField setCenter:originalCenter];
}
Problem: While this return correct size results at first its becomes more and more unprecise by adding characters therefore finally starts clipping the string (as seen in the right screenshot).
How do I get the accurate size of the textField string for correctly adjusting its size?
UITextField has it's own layout inside if you use borderStyle != UITextBorderStyleNone. In this case you have to increase text size dimensions by some constants.
With UITextBorderStyleNone you don't have this problem, and code below works like a charm (iOS 7 introduced new method to get text size, -sizeWithFont: is deprecated)
- (IBAction)textChanged:(UITextField *)field
{
UIFont *font = field.font;
NSString *string = field.text;
CGSize size = [string sizeWithAttributes:
[NSDictionary dictionaryWithObject:font forKey:NSFontAttributeName]];
CGPoint center = field.center;
CGRect frame = field.frame;
frame.size = size; // or CGSizeMake(size.width + WIDTH_PADDING * 2, size.height + HEIGHT_PADDING * 2)
field.frame = frame;
field.center = center;
}
the problem is that you don't take into account the contentInset of the UITextField. Your code would be fine for a label not for a textfield.
for example: one way could be:
CGPoint originalCenter = self.textField.center;
UIFont* currentFont = [textField font];
CGSize oldSize = [self.textField.text sizeWithFont:currentFont];
CGSize newSize = [textFieldString sizeWithFont:currentFont];
CGRect finalFrame = self.textField.frame
finalFrame.size.width -= oldSize.width;
finalFrame.size.width += newSize.width;
finalFrame.size.height -= oldSize.height;
finalFrame.size.height += newSize.height;
[self.textField setFrame:finalFrame];
[self.textField setCenter:originalCenter];
ios7 deprecates sizeWithFont:currentFont so it is sizeWithAttributes:#{NSFontAttributeName:currentFont}

Dynamically changing font size of UILabel

I currently have a UILabel:
factLabel = [[UILabel alloc] initWithFrame:CGRectMake(20, 100, 280, 100)];
factLabel.text = #"some text some text some text some text";
factLabel.backgroundColor = [UIColor clearColor];
factLabel.lineBreakMode = UILineBreakModeWordWrap;
factLabel.numberOfLines = 10;
[self.view addSubview:factLabel];
Throughout the life of my iOS application, factLabel gets a bunch of different values. Some with multiple sentences, others with just 5 or 6 words.
How can I set up the UILabel so that the font size changes so that the text always fits in the bounds I defined?
Single line:
factLabel.numberOfLines = 1;
factLabel.minimumFontSize = 8;
factLabel.adjustsFontSizeToFitWidth = YES;
The above code will adjust your text's font size down to (for example) 8 trying to fit your text within the label.
numberOfLines = 1 is mandatory.
Multiple lines:
For numberOfLines > 1 there is a method to figure out the size of final text through NSString's sizeWithFont:... UIKit addition methods, for example:
CGSize lLabelSize = [yourText sizeWithFont:factLabel.font
forWidth:factLabel.frame.size.width
lineBreakMode:factLabel.lineBreakMode];
After that you can just resize your label using resulting lLabelSize, for example (assuming that you will change only label's height):
factLabel.frame = CGRectMake(factLabel.frame.origin.x, factLabel.frame.origin.y, factLabel.frame.size.width, lLabelSize.height);
iOS6
Single line:
Starting with iOS6, minimumFontSize has been deprecated. The line
factLabel.minimumFontSize = 8.;
can be changed to:
factLabel.minimumScaleFactor = 8./factLabel.font.pointSize;
iOS7
Multiple lines:
Starting with iOS7, sizeWithFont becomes deprecated.
Multiline case is reduced to:
factLabel.numberOfLines = 0;
factLabel.lineBreakMode = NSLineBreakByWordWrapping;
CGSize maximumLabelSize = CGSizeMake(factLabel.frame.size.width, CGFLOAT_MAX);
CGSize expectSize = [factLabel sizeThatFits:maximumLabelSize];
factLabel.frame = CGRectMake(factLabel.frame.origin.x, factLabel.frame.origin.y, expectSize.width, expectSize.height);
iOS 13 (Swift 5):
label.adjustsFontSizeToFitWidth = true
label.minimumScaleFactor = 0.5
minimumFontSize has been deprecated with iOS 6. You can use minimumScaleFactor.
yourLabel.adjustsFontSizeToFitWidth=YES;
yourLabel.minimumScaleFactor=0.5;
This will take care of your font size according width of label and text.
Single line- There are two ways, you can simply change.
1- Pragmatically (Swift 3)
Just add the following code
yourLabel.numberOfLines = 1;
yourLabel.minimumScaleFactor = 0.7;
yourLabel.adjustsFontSizeToFitWidth = true;
2 - Using UILabel Attributes inspector
i- Select your label- Set number of lines 1.
ii- Autoshrink- Select Minimum Font Scale from drop down
iii- Set Minimum Font Scale value as you wish , I have set 0.7 as in below image. (default is 0.5)
Based on #Eyal Ben Dov's answer you may want to create a category to make it flexible to use within another apps of yours.
Obs.: I've updated his code to make compatible with iOS 7
-Header file
#import <UIKit/UIKit.h>
#interface UILabel (DynamicFontSize)
-(void) adjustFontSizeToFillItsContents;
#end
-Implementation file
#import "UILabel+DynamicFontSize.h"
#implementation UILabel (DynamicFontSize)
#define CATEGORY_DYNAMIC_FONT_SIZE_MAXIMUM_VALUE 35
#define CATEGORY_DYNAMIC_FONT_SIZE_MINIMUM_VALUE 3
-(void) adjustFontSizeToFillItsContents
{
NSString* text = self.text;
for (int i = CATEGORY_DYNAMIC_FONT_SIZE_MAXIMUM_VALUE; i>CATEGORY_DYNAMIC_FONT_SIZE_MINIMUM_VALUE; i--) {
UIFont *font = [UIFont fontWithName:self.font.fontName size:(CGFloat)i];
NSAttributedString *attributedText = [[NSAttributedString alloc] initWithString:text attributes:#{NSFontAttributeName: font}];
CGRect rectSize = [attributedText boundingRectWithSize:CGSizeMake(self.frame.size.width, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin context:nil];
if (rectSize.size.height <= self.frame.size.height) {
self.font = [UIFont fontWithName:self.font.fontName size:(CGFloat)i];
break;
}
}
}
#end
-Usage
#import "UILabel+DynamicFontSize.h"
[myUILabel adjustFontSizeToFillItsContents];
Cheers
It's 2015. I had to go to find a blog post that would explain how to do it for the latest version of iOS and XCode with Swift so that it would work with multiple lines.
set “Autoshrink” to “Minimum font size.”
set the font to the largest desirable font size (I chose 20)
Change “Line Breaks” from “Word Wrap” to “Truncate Tail.”
Source:
http://beckyhansmeyer.com/2015/04/09/autoshrinking-text-in-a-multiline-uilabel/
Swift version:
textLabel.adjustsFontSizeToFitWidth = true
textLabel.minimumScaleFactor = 0.5
Here's a Swift extension for UILabel. It runs a binary search algorithm to resize the font based off the width and height of the label's bounds. Tested to work with iOS 9 and autolayout.
USAGE: Where <label> is your pre-defined UILabel that needs font resizing
<label>.fitFontForSize()
By Default, this function searches in within the range of 5pt and 300pt font sizes and sets the font to fit its text "perfectly" within the bounds (accurate within 1.0pt). You could define the parameters so that it, for example, searches between 1pt and the label's current font size accurately within 0.1pts in the following way:
<label>.fitFontForSize(1.0, maxFontSize: <label>.font.pointSize, accuracy:0.1)
Copy/Paste the following code into your file
extension UILabel {
func fitFontForSize(var minFontSize : CGFloat = 5.0, var maxFontSize : CGFloat = 300.0, accuracy : CGFloat = 1.0) {
assert(maxFontSize > minFontSize)
layoutIfNeeded() // Can be removed at your own discretion
let constrainedSize = bounds.size
while maxFontSize - minFontSize > accuracy {
let midFontSize : CGFloat = ((minFontSize + maxFontSize) / 2)
font = font.fontWithSize(midFontSize)
sizeToFit()
let checkSize : CGSize = bounds.size
if checkSize.height < constrainedSize.height && checkSize.width < constrainedSize.width {
minFontSize = midFontSize
} else {
maxFontSize = midFontSize
}
}
font = font.fontWithSize(minFontSize)
sizeToFit()
layoutIfNeeded() // Can be removed at your own discretion
}
}
NOTE: Each of the layoutIfNeeded() calls can be removed at your own discretion
Its a little bit not sophisticated but this should work,
for example lets say you want to cap your uilabel to 120x120, with max font size of 28:
magicLabel.numberOfLines = 0;
magicLabel.lineBreakMode = NSLineBreakByWordWrapping;
...
magicLabel.text = text;
for (int i = 28; i>3; i--) {
CGSize size = [text sizeWithFont:[UIFont systemFontOfSize:(CGFloat)i] constrainedToSize:CGSizeMake(120.0f, CGFLOAT_MAX) lineBreakMode:NSLineBreakByWordWrapping];
if (size.height < 120) {
magicLabel.font = [UIFont systemFontOfSize:(CGFloat)i];
break;
}
}
Just send the sizeToFit message to the UITextView. It will adjust its own height to just fit its text. It will not change its own width or origin.
[textViewA1 sizeToFit];
Swift 2.0 Version:
private func adapteSizeLabel(label: UILabel, sizeMax: CGFloat) {
label.numberOfLines = 0
label.lineBreakMode = NSLineBreakMode.ByWordWrapping
let maximumLabelSize = CGSizeMake(label.frame.size.width, sizeMax);
let expectSize = label.sizeThatFits(maximumLabelSize)
label.frame = CGRectMake(label.frame.origin.x, label.frame.origin.y, expectSize.width, expectSize.height)
}
This solution works for multiline:
After following several articles, and requiring a function that would automatically scale the text and adjust the line count to best fit within the given label size, I wrote a function myself. (ie. a short string would fit nicely on one line and use a large amount of the label frame, whereas a long strong would automatically split onto 2 or 3 lines and adjust the size accordingly)
Feel free to re-use it and tweak as required. Make sure you call it after viewDidLayoutSubviews has finished so that the initial label frame has been set.
+ (void)setFontForLabel:(UILabel *)label withMaximumFontSize:(float)maxFontSize andMaximumLines:(int)maxLines {
int numLines = 1;
float fontSize = maxFontSize;
CGSize textSize; // The size of the text
CGSize frameSize; // The size of the frame of the label
CGSize unrestrictedFrameSize; // The size the text would be if it were not restricted by the label height
CGRect originalLabelFrame = label.frame;
frameSize = label.frame.size;
textSize = [label.text sizeWithAttributes:#{NSFontAttributeName:[UIFont systemFontOfSize: fontSize]}];
// Work out the number of lines that will need to fit the text in snug
while (((textSize.width / numLines) / (textSize.height * numLines) > frameSize.width / frameSize.height) && (numLines < maxLines)) {
numLines++;
}
label.numberOfLines = numLines;
// Get the current text size
label.font = [UIFont systemFontOfSize:fontSize];
textSize = [label.text boundingRectWithSize:CGSizeMake(frameSize.width, CGFLOAT_MAX)
options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
attributes:#{NSFontAttributeName : label.font}
context:nil].size;
// Adjust the frame size so that it can fit text on more lines
// so that we do not end up with truncated text
label.frame = CGRectMake(label.frame.origin.x, label.frame.origin.y, label.frame.size.width, label.frame.size.width);
// Get the size of the text as it would fit into the extended label size
unrestrictedFrameSize = [label textRectForBounds:CGRectMake(0, 0, label.bounds.size.width, CGFLOAT_MAX) limitedToNumberOfLines:numLines].size;
// Keep reducing the font size until it fits
while (textSize.width > unrestrictedFrameSize.width || textSize.height > frameSize.height) {
fontSize--;
label.font = [UIFont systemFontOfSize:fontSize];
textSize = [label.text boundingRectWithSize:CGSizeMake(frameSize.width, CGFLOAT_MAX)
options:(NSStringDrawingUsesLineFragmentOrigin|NSStringDrawingUsesFontLeading)
attributes:#{NSFontAttributeName : label.font}
context:nil].size;
unrestrictedFrameSize = [label textRectForBounds:CGRectMake(0, 0, label.bounds.size.width, CGFLOAT_MAX) limitedToNumberOfLines:numLines].size;
}
// Set the label frame size back to original
label.frame = originalLabelFrame;
}
Here is the fill code of a UILabel subclass that implements animated font size change:
#interface SNTextLayer : CATextLayer
#end
#implementation SNTextLayer
- (void)drawInContext:(CGContextRef)ctx {
// We override this to make text appear at the same vertical positon as in UILabel
// (otherwise it's shifted tdown)
CGFloat height = self.bounds.size.height;
float fontSize = self.fontSize;
// May need to adjust this somewhat if it's not aligned perfectly in your implementation
float yDiff = (height-fontSize)/2 - fontSize/10;
CGContextSaveGState(ctx);
CGContextTranslateCTM(ctx, 0.0, yDiff);
[super drawInContext:ctx];
CGContextRestoreGState(ctx);
}
#end
#interface SNAnimatableLabel ()
#property CATextLayer* textLayer;
#end
#interface SNAnimatableLabel : UILabel
- (void)animateFontToSize:(CGFloat)fontSize withDuration:(double)duration;
#end
#implementation SNAnimatableLabel
- (void)awakeFromNib {
[super awakeFromNib];
_textLayer = [SNTextLayer new];
_textLayer.backgroundColor = self.backgroundColor.CGColor;
_textLayer.foregroundColor = self.textColor.CGColor;
_textLayer.font = CGFontCreateWithFontName((CFStringRef)self.font.fontName);
_textLayer.frame = self.bounds;
_textLayer.string = self.text;
_textLayer.fontSize = self.font.pointSize;
_textLayer.contentsScale = [UIScreen mainScreen].scale;
[_textLayer setPosition: CGPointMake(CGRectGetMidX(_textLayer.frame), CGRectGetMidY(_textLayer.frame))];
[_textLayer setAnchorPoint: CGPointMake(0.5, 0.5)];
[_textLayer setAlignmentMode: kCAAlignmentCenter];
self.textColor = self.backgroundColor;
// Blend text with background, so that it doens't interfere with textlayer text
[self.layer addSublayer:_textLayer];
self.layer.masksToBounds = NO;
}
- (void)setText:(NSString *)text {
_textLayer.string = text;
super.text = text;
}
- (void)layoutSubviews {
[super layoutSubviews];
// Need to enlarge the frame, otherwise the text may get clipped for bigger font sizes
_textLayer.frame = CGRectInset(self.bounds, -5, -5);
}
- (void)animateFontToSize:(CGFloat)fontSize withDuration:(double)duration {
[CATransaction begin];
[CATransaction setAnimationDuration:duration];
_textLayer.fontSize = fontSize;
[CATransaction commit];
}

Resources