Calculating font size for a CCLabelTTF - objective-c

Does anyone know a method to calculate the maximum font size for a CCLabelTTf of a specific CGSize? I have seen ways of calculating a font size for a specific width but not width and height. This calculates the font size for width:
-(int) getSizeForString:(NSString*)aString InWidth:(int)width
{
int startSize = 24;
while (startSize > 5) {
CGSize aSize = [aString sizeWithFont:[UIFont fontWithName:#"Verdana-Bold" size:startSize]];
if (aSize.width <= width) return startSize;
startSize--;
}
return 5;
}

If you know how to calculate size of text with a specific font, you can in the same way check height...
If you want to set string in more than one line you should use
[NSString sizeWithFont:font constrainedToSize:maxSize lineBreakMode:NSLineBreakByClipping];
This return you a CGSize for string with more than one lines if the text don't fit to maxSize

Related

Efficiently determine how much text can fit in a UILabel in IOS

I have an NSString and I want to know how much of that string will fit into a UILabel.
My code builds a test string by adding one character at a time from my original string. Each time I add a character I test the new string to see if it will fit into my label:
CGRect cutTextRect = [cutText boundingRectWithSize:maximumLabelSize options:NSStringDrawingUsesLineFragmentOrigin attributes:stringAttributes context:nil];
Then I compare the height of that rect to the height of my label to see if the string overflowed.
This works, but instruments shows me that the loop is taking up all my cpu time.
Can anyone think of or know of a faster way to do this?
Thanks!
While not the prettiest:
- (NSString *)stringForWidth:(CGFloat)width fullString:(NSString *)fullString
{
NSDictionary *attributes = #{NSFontAttributeName: label.font};
if ([fullString sizeWithAttributes:attributes].width <= width)
{
return fullString;
}
// Might be worth researching more regarding 'average' char size
CGFloat approxCharWidth = [#"N" sizeWithAttributes:attributes].width;
NSInteger approxNumOfChars = (NSInteger)(width / approxCharWidth);
NSMutableString *resultingString = [NSMutableString stringWithString:[fullString substringToIndex:approxNumOfChars]];
CGFloat currentWidth = [resultingString sizeWithAttributes:attributes].width;
if (currentWidth < width)
{
// Try to 'sqeeze' another char.
while (currentWidth < width && approxNumOfChars < fullString.length)
{
approxNumOfChars++;
[resultingString appendString:[fullString substringWithRange:NSMakeRange(approxNumOfChars - 1, 1)]];
currentWidth = [resultingString sizeWithAttributes:attributes].width;
}
}
// String might be oversized
if (currentWidth > width)
{
while (currentWidth > width)
{
[resultingString deleteCharactersInRange:NSMakeRange(resultingString.length - 1, 1)];
currentWidth = [resultingString sizeWithAttributes:attributes].width;
}
}
// If dealing with UILabel, it's safer to have a smaller string than 'equal',
// 'clipping wise'. Otherwise, just use '<=' or '>=' instead of '<' or '>'
return [NSString stringWithString:resultingString];
}
There are a couple of loops, but each one is a 'fine tuning' and should only run a small number of times.
One way to improve efficiency is to get a better starting point than calculating be how many N's can fit in a given width.
I'm open to suggestions about that.
-Edit:
Regarding multiline label, once I know a given text for width, I can expect the following text (if any) will go to the next line.
In other words, getting 'text for width' is the tricky part, 'width for text' we get for free.

NSImage -initWithContentsOfFile returns an image of size zero (0)

To load icon images, I have the below code in one of the methods:
NSLog(#"icon path: %#", iconPath);
NSImage *iconImage = [[NSImage alloc] initWithContentsOfFile:iconPath];
return iconImage;
From the log output, it is clear that image resources are being opened from the correct location. I don't see errors. Yet, tif files that I open are shown to have empty NSSize (width=0, height=0) in debugger, and displayed on the screen as if I am pointing to some runaway memory segment.
Flags are mainly set to 0. The exceptions are colorMatchPreferred and multipleResolutionMatching set to 1.
Reps points to an array (NSArrayM *) containing two (2) bitmap representations (NSBitmapImageRep entries).
Please advise what am I doing wrong!
Thank you
//to get image original size use this code
NSArray * imageReps = [NSBitmapImageRep imageRepsWithContentsOfFile:fileUrl];
NSInteger width = 0;
NSInteger height = 0;
for (NSImageRep * imageRep in imageReps)
{
if ([imageRep pixelsWide] > width)
width = [imageRep pixelsWide];
if ([imageRep pixelsHigh] > height)
height = [imageRep pixelsHigh];
}

NSString font size specific to frame width

I am using drawRect for a text display, calling NSString. I am trying to implement using sizeWithFont to auto resizing font (shrinking) with default font size of 17 and using a loop to reduce the font size by 1 if it does not fit the size of width. Can anyone help me how to implement this? Example would be nice right now I just have the font size set to 17.0
[[self.string displayName] drawAtPoint:CGPointMake(xcoord, ycoord) withFont:[UIFont boldSystemFontOfSize:17.0]];
CGSize size = [[self.patient displayName] sizeWithFont:[UIFont boldSystemFontOfSize:17.0]];
max_current_y = size.height > max_current_y ? size.height : max_current_y;
xcoord = xcoord + 3.0f + size.width;
OK never mind. Here's modified version of the same method that takes NSString for which to return a font:
-(UIFont*)getFontForString:(NSString*)string
toFitInRect:(CGRect)rect
seedFont:(UIFont*)seedFont{
UIFont* returnFont = seedFont;
CGSize stringSize = [string sizeWithAttributes:#{NSFontAttributeName : seedFont}];
while(stringSize.width > rect.size.width){
returnFont = [UIFont systemFontOfSize:returnFont.pointSize -1];
stringSize = [string sizeWithAttributes:#{NSFontAttributeName : returnFont}];
}
return returnFont;
}
Here's how to call it:
NSString* stringToDraw = #"Test 123";
CGRect rect = CGRectMake(100., 100., 100., 200.);
UIFont* font = [self getFontForString:stringToDraw toFitInRect:rect seedFont:[UIFont systemFontOfSize:20]];
[stringToDraw drawInRect:rect withFont:font];
Code is for iOS7+
Trying font sizes with step 1.0 may be very slow. You can tremendously improve the algorithm by making two measures for two different sizes, then using linear approximation to guess the size that will be very close to the right one.
If it turns out not close enough, repeat the calculation using the guessed size instead of one of the previous two until it is good enough or stops changing:
// any values will do, prefer those near expected min and max
CGFloat size1 = 12.0, size2 = 56.0;
CGFloat width1 = measure_for_size(size1);
CGFloat width2 = measure_for_size(size2);
while (1) {
CGFloat guessed_size = size1 + (required_width - width1) * (size2 - size1) / (width2 - width1);
width2 = measure_for_size(guessed_size);
if ( fabs(guessed_size-size2) < some_epsilon || !is_close_enough(width2, required_width) ) {
size2 = guessed_size;
continue;
}
// round down to integer and clamp guessed_size as appropriate for your design
return floor(clamp(guessed_size, 6.0, 24.0));
}
is_close_enough() implementation is completely up to you. Given that text width grows almost linearly of font size, you can simply drop it and just do 2-4 iterations which should be enough.
I wanted to try to make a version that didn't have to repeatedly check font sizes using a do...while loop. Instead, I assumed that font point sizes were a linear scale, then worked out the size difference between the required frame width and the actual frame width, then adjusted the font size accordingly. Therefore, I ended up with this function:
+ (CGFloat)fontSizeToFitString:(NSString *)string inWidth:(float)width withFont:(UIFont *)font
{
UILabel *label = [UILabel new];
label.font = font;
label.text = string;
[label sizeToFit];
float ratio = width / label.frame.size.width;
return font.pointSize * ratio;
}
Pass in a font of any size, as well as the string and the required width, and it will return you the point size for that font.
I also wanted to take it a bit further and find out the font size for a multi-line string, so that the longest line would fit without a line break:
+ (CGFloat)fontSizeToFitLongestLineOfString:(NSString *)string inWidth:(float)width withFont:(UIFont *)font
{
NSArray *stringLines = [string componentsSeparatedByString:#"\n"];
UILabel *label = [UILabel new];
label.font = font;
float maxWidth = 0;
for(NSString *line in stringLines)
{
label.text = line;
[label sizeToFit];
maxWidth = MAX(maxWidth, label.frame.size.width);
}
float ratio = width / maxWidth;
return font.pointSize * ratio;
}
Seems to work perfectly fine for me. Hope it helps someone else.
Original poster didn't specify what platform he was working on, but for OSX developers on Mavericks, sizeWithFont: doesn't exist and one should use sizeWithAttributes :
NSSize newSize = [aString sizeWithAttributes:
[NSDictionary dictionaryWithObjectsAndKeys:
[NSFont fontWithName:#"Arial Rounded MT Bold" size:53.0],NSFontAttributeName,nil
]];
Here's a method which can return you font that will fit in a rect:
-(UIFont*)getFontToFitInRect:(CGRect)rect seedFont:(UIFont*)seedFont{
UIFont* returnFont = seedFont;
CGSize stringSize = [self sizeWithFont:returnFont];
while(stringSize.width > rect.size.width){
returnFont = [UIFont systemFontOfSize:returnFont.pointSize -1];
stringSize = [self sizeWithFont:returnFont];
}
return returnFont;
}
You can add this method to a NSString category. You can find more about how to add a category here: http://developer.apple.com/library/ios/#documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/CustomizingExistingClasses/CustomizingExistingClasses.html#//apple_ref/doc/uid/TP40011210-CH6-SW2
If you don't want to create a category, you can add this method to one of your utility classes and pass in the string for which you want the font to be returned.
Here is another method, inspired by #puru020 & #jowie answers. Hope it helps someone
-(UIFont *) adjustedFontSizeForString:(NSString *)string forWidth:(float)originalWidth forFont:(UIFont *)font
{
CGSize stringSize = [string sizeWithFont:font];
if(stringSize.width <= originalWidth)
{
return font;
}
float ratio = originalWidth / stringSize.width;
float fontSize = font.pointSize * ratio;
return [font fontWithSize:fontSize];
}
I modified a bit the solution of #puru020 , added the support for attributes, and improved a bit:
Note: The method should be wrapped in a NSString Category
- (UIFont*)requiredFontToFitInSize:(CGSize)size seedFont:(UIFont*)seedFont attributes:(NSDictionary*)attributes{
UIFont *returnFont = [UIFont systemFontOfSize:seedFont.pointSize +1];
NSMutableDictionary *mutableAttributes = attributes.mutableCopy;
CGSize stringSize;
do {
returnFont = [UIFont systemFontOfSize:returnFont.pointSize -1];
[mutableAttributes setObject:returnFont forKey:NSFontAttributeName];
stringSize = [self sizeWithAttributes:mutableAttributes];
} while (stringSize.width > size.width);
return returnFont;
}

Average Color of Mac Screen

I'm trying to find out a way to calculate the average color of the screen using objective-c.
So far I use this code to get a screen shot, which works great:
CGImageRef image1 = CGDisplayCreateImage(kCGDirectMainDisplay);
NSBitmapImageRep *bitmapRep = [[NSBitmapImageRep alloc] initWithCGImage:image1];
// Create an NSImage and add the bitmap rep to it...
NSImage *image = [[NSImage alloc] init];
[image addRepresentation:bitmapRep];
Now my problem is to calculate the average RGB color of this image.
I've found one solution, but the R G and B color components were always calculated to be the same (equal):
NSInteger i = 0;
NSInteger components[3] = {0,0,0};
unsigned char *data = [bitmapRep bitmapData];
NSInteger pixels = ([bitmapRep size].width *[bitmapRep size].height);
do {
components[0] += *data++;
components[1] += *data++;
components[2] += *data++;
} while (++i < pixels);
int red = (CGFloat)components[0] / pixels;
int green = (CGFloat)components[1] / pixels;
int blue = (CGFloat)components[2] / pixels;
A short analysis of bitmapRep shows that each pixel has 32 bits (4 bytes) where the first byte is unused, it is a padding byte, in other words the format is XRGB and X is not used. (There are no padding bytes at the end of a pixel row).
Another remark: for counting the number of pixels you use the method -(NSSize)size.
You should never do this! size has nothing to do with pixels. It only says how big the image should be depicted (expressed in inch or cm or mm) on the screen or the printer. For counting (or using otherwise) the pixels you should use -(NSInteger)pixelsWide and -(NSInteger)pixelsHigh. But the (wrong) using of -size works if and only if the resolution of the imageRep is 72 dots per inch.
Finally: there is a similar question at Average Color of Mac Screen
Your data is probably aligned as 4 bytes per pixel (and not 3 bytes, like you assume). That would (statistically) explain the near-equal values that you get.

Is there a way to obtain the length of the cell's subtitle (detailedTextLabel)?

as mentioned in the question, I would like to obtain the length of the subtitle (topic.context) and use it to make certain decisions (see my code snippet below)
cell.detailTextLabel.text = topic.context;
NSString *fanLabelText = [NSString stringWithFormat:#"%i fans",topic.num_fans];
if (topic.context && ![topic.context isEqual:[NSNull null]] && topic.context.length > 46)
{
thisFanLabel.frame = CGRectMake(320 - 150, -10, 100, 44);
}
else
{
thisFanLabel.frame = CGRectMake(320 - 150, 0, 100, 44);
}
Basically, I want to know when the subtitle will reach a certain length so that I can adjust the fan label to be shifted upwards (as seen in the diagram below). Currently the fans label is being overlapped by the subtitle, I want to be able to shift the label upwards when this happens.
So what would be the best way to obtain the accurate length of the subtitle?
You can check the size of a string with a certain font by using the following lines:
CGSize maxSize = CGSizeMake(9999,9999);
UILabel *myLabel = cell.detailTextLabel;
CGSize sizeOfString = [myLabel.text sizeWithFont:myLabel.font
constrainedToSize:maxSize
lineBreakMode:myLabel.lineBreakMode];
In "sizeOfString" you should now have the size of your detailLabel.
See NSString's -sizeWithFont: and related methods.