Dynamic CCLabelTTF font size in a specified rectangle - cocos2d - ios

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];

Related

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

Autoadjust content of UILabel

I have already determined the bounds of UILabel. Single line, 2-3 words. I have to change the font size. What is the best solution to adjust the content into strongly determined rectangle programmatically?
Set the minimum font scale of the label
_myLabel.adjustsFontSizeToFitWidth = YES;
_myLabel.minimumScaleFactor = 0.5f;
Use Autoshrink property in your XIB (or programmatically) and set minimum Font Size and your text will be adapted to the space. But with a minimum font.
I found the solution to check the sizes manually. The other solutions are working improperly. May be this code will help to somebody. Here the code to solve:
-(void) autoResizeLabel: (UILabel*) label withMaxWidth:(CGFloat)mw withMaxHeight:(CGFloat)mh
{
CGFloat fontSize = 1.f;
CGFloat outSize = fontSize;
CGFloat mDelta = 30.f;
CGFloat delta = 1.f;
BOOL activated = NO;
BOOL broken = NO;
for (float fSize = fontSize; fSize < fontSize + mDelta; fSize += delta)
{
CGRect r = [label.text boundingRectWithSize:label.frame.size options:NSStringDrawingUsesLineFragmentOrigin attributes:#{NSFontAttributeName:[UIFont systemFontOfSize:fSize]} context:nil];
CGFloat width = r.size.width;
CGFloat height = r.size.height;
if (mw <= width || mh <= height)
{
if (activated)
{
outSize = fSize - delta;
}
broken = YES;
break;
}
activated = YES;
//NSLog(#"%f;%f;%f",fSize,mw,mh);
}
if (activated && !broken) outSize = fontSize + mDelta - delta;
[label setFont:[UIFont systemFontOfSize:outSize]];
}

iOS drawing text in a given width

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;
}

Dyamic frame calculation for UIImages in UITableViewCell

I am putting few icons on UITableViewCell. The number of icons can vary from 1 to 6. Icons should not have fixed X position and instead they should be dynamically placed considering the cell space. So if there are 3 icons they should look placed centric to the cell. Also, the icon spacing should also vary. For instance when it is 6 icons all of them will be placed with less margins in between and when there are 3 then margin will be more and so will be the X position.
Please suggest some quick way to calculate this frame. I am running the app both on iOS 6 and iOS 7.
This is what I have tried by far but this does not seems to work well with icon count variation. Also, in between space is not dynamic with this.
int maxIconTypes = 6;
CGFloat innerPadding = ([self isIOS7]) ? 15.0f : 9.0f;
CGRect adjustedBoundsIOS6 = [self bounds];
CGFloat adjustedWidth = ([self isIOS7]) ? self.bounds.size.width : adjustedBoundsIOS6.size.width;
CGFloat xOrigin = innerPadding;
CGFloat iconViewSize = 25.0f;
CGFloat ySpacer = (self.bounds.size.height - iconSize) / 2;
CGFloat xSpacer = ((adjustedWidth - (innerPadding * 2)) - (maxRequestTypes * iconViewSize)) / (maxIconTypes - 1);
for (NSString *icon in iconList) {
UIImageView *anIconView = [[UIImageView alloc] initWithImage:[UIImage icon]];
if (anIconView.image) {
anIconView.frame = CGRectMake(xOrigin + originPadding, ySpacer, anIconView.image.size.width, anIconView.image.size.height);
[self.contentView addSubview:anIconView];
xOrigin += anIconView.frame.size.width + xSpacer;
}
}
However many items you have, the number of spaces is the same if you want equal spacing with half spacing at the start and end:
half width full width half
half width full width full width half
So, you just need to know the full width available and the combined width of the items. A simple multiply (to get the combined width of the items) and subtract (from the full width available) gives you the remaining width for the 'spacers'. Divide by the number of items to get the xSpacer, set the initial xOrigin to xSpacer * 0.5
Here is an example of what Wain is explaining:
//Im doing this in a UIView rather than in a UITableViewCell but idea is the same
int cellWidth = CGRectGetWidth(self.view.bounds);
//num of icons
int iconCount = 6;
//size of icon
int iconSize = 25;
//The max padding we can have
float maxPadding = (cellWidth - (iconSize * iconCount)) / iconCount;
//Our offset
float xOrigin = maxPadding / 2;
//Loooop
for (int i = 0; i < iconCount; i++) {
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(xOrigin, self.view.center.y, iconSize, iconSize)];
xOrigin += (iconSize + maxPadding);
label.backgroundColor = [UIColor blueColor];
[self.view addSubview:label];
}

How to measure the max size of the largest character in a given font type and font size?

I want to cache some textures of characters of a given font type and font size, but I can't make sure of the max size the largest char may take in the whole unicode char set.
How can I calculate this size?
What you're looking for is the Font Bounding Box (or BBox). In iOS, you'll need a CGFontRef and can then use the function:
CGRect CGFontGetFontBBox (
CGFontRef font
);
This returns the font bounding box, which is the union of all glyph bounding boxes in the font. It returns the value in glyph units.
well, you may use sizeWithFont:
and check the largest (or tallest or biggest in area)
it works well for width and area, but it seems that all characters have got the same height, even dots "."
anyway, i guess that something like that should answer your question:
UIFont* aFont = [UIFont fontWithName:#"HelveticaNeue" size:15];
NSString* charsToTest = #"abcdf...xyz, ABCD...XYZ, 0123...";
float maxArea = 0;
float maxWidth = 0;
float maxHeight = 0;
NSString* largerChar;
NSString* tallerChar;
NSString* biggerChar;
for (int i = 0; i<charsToTest.length; i++) {
NSRange currentRange;
currentRange.length = 1;
currentRange.location = i;
NSString* currentCharToTest = [charsToTest substringWithRange:currentRange];
CGSize currentSize = [currentCharToTest sizeWithFont:aFont];
if (currentSize.width > maxWidth) {
maxWidth = currentSize.width;
largerChar = currentCharToTest;
NSLog(#"char:%#, new record width: %f", largerChar, maxWidth);
}
if (currentSize.height > maxHeight) {
maxHeight = currentSize.height;
tallerChar = currentCharToTest;
NSLog(#"char:%#, new record height:%f", tallerChar, maxHeight);
}
float currentArea = currentSize.height * currentSize.width;
if ( currentArea > maxArea) {
maxArea = currentArea;
biggerChar = currentCharToTest;
NSLog(#"char:%#, new area record: %f", biggerChar, maxArea);
}
}
// final resut:
NSLog(#"\n\n");
NSLog(#"char:%# --> MAX WIDTH IS: %f", largerChar, maxWidth);
NSLog(#"char:%# --> MAX HEIGHT IS: %f", tallerChar, maxHeight);
NSLog(#"char:%# --> MAX AREA IS: %f\n\n", biggerChar, maxArea);

Resources