How to get UILabel (UITextView) auto adjusted font size? - iphone

Is it possible to get final font size, after autoadjusting? (property adjustsFontSizeToFitWidth set to YES, and text font size is being shrinked to fit into the label)
I am subclassing drawTextInRect in UILabel to put gradient on the text, but the gradient size needs to be the same, as the size of the font. I am not able to get proper size of the adjusted font...Is it even possible?
//draw gradient
CGContextSaveGState(myContext);
CGGradientRef glossGradient;
CGColorSpaceRef rgbColorspace;
size_t num_locations = 2;
CGFloat locations[2] = { 0.0, 1.0 };
CGFloat components[8] = { 1, 1, 1, 0.25, // BOTTOM color
1, 1, 1, 0.12 }; // UPPER color
//scale and translate so that text would not be rotated 180 deg wrong
CGContextTranslateCTM(myContext, 0, rect.size.height);
CGContextScaleCTM(myContext, 1.0, -1.0);
//create mask
CGImageRef alphaMask = CGBitmapContextCreateImage(myContext);
CGContextClipToMask(myContext, rect, alphaMask);
rgbColorspace = CGColorSpaceCreateDeviceRGB();
glossGradient = CGGradientCreateWithColorComponents(rgbColorspace, components, locations, num_locations);
//gradient should be sized to actual font size. THIS IS THE PROBLEM - EVEN IF FONT IS AUTO ADUJSTED, I AM GETTING THE SAME ORIGINAL FONT SIZE!!!
CGFloat fontCapHeightHalf = (self.font.capHeight/2)+5;
CGRect currentBounds = rect;
CGPoint topCenter = CGPointMake(CGRectGetMidX(currentBounds), CGRectGetMidY(currentBounds)-fontCapHeightHalf);
CGPoint midCenter = CGPointMake(CGRectGetMidX(currentBounds), CGRectGetMidY(currentBounds)+fontCapHeightHalf);
CGContextDrawLinearGradient(myContext, glossGradient, topCenter, midCenter, 0);
CGGradientRelease(glossGradient);
CGColorSpaceRelease(rgbColorspace);
CGContextRestoreGState(myContext);

You can't get the size directly, but you can calculate it easily enough using these functions:
CGFloat actualFontSize;
[label.text sizeWithFont:label.font
minFontSize:label.minimumFontSize
actualFontSize:&actualFontSize
forWidth:label.bounds.size.width
lineBreakMode:label.lineBreakMode];

CGSize size = [label.text sizeWithFont:label.font minFontSize:10 actualFontSize:&actualFontSize forWidth:200 lineBreakMode:UILineBreakModeTailTruncation];

My answer isn't very helpful for the original question, but I end here every time I search how to get the font size after auto adjust.
First of all, sizeWithFont has been deprecated in iOS 7.0, as we all know, so we have to find another solution.
My solution fits my case, in which I have two labels, one with more constraints and more text than second one.
Here is a step-by-step example:
two labels with same width and different text length:
I want, at run-time, the font size of the main uilabel to be resized and the second one to have the same font size.
decrease the size of both labels, so that the text of the first one fits the frame, in this case 12 for both:
after having set up the position constraints (in my example, the labels stay in the center of the parent view), update the both labels' frames so that they fit the text exactly:
add the Aspect Ratio constraints between width and height to both labels and the width constraint between the main label and the parent view:
add the width constraint from the second label to the main label:
now you can set font size of the labels as you wish:
Here is a screenshot of the Simulator:
The first label is resized according to the screen size and the font size is adjusted consequently. The font size of the second label is the same due to the width constraint and the font's parameters specified in the last image.
This is only an example that aims to showcase the "trick" behind my solution: bind the frames of the labels at an initial font size, so that it will be the same at run-time. I imagine that this technique could be re-adapted to labels with a variable size, just by adding the constraints between labels after having set the text and having resized the frame to fit the text.

This simple solution works fine 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;

Related

Monotouch - calculate UILabel height

I am trying to create a custom cell which consists a few UILabels.
The first label might take one or more rows, so I need to resize the label according to the number of lines (after setting the number of lines to 0, so multi-line will be enabled).
I have tried setting sizeToFit(), but it changed the alignment and width of my label.
I found this answer
but I don't know how to convert it to C#.
Can anyone point me to an example? (I already tried Googling it off-course)
This is the method from the link:
// UILabel *myLabel;
CGSize labelSize = [myLabel.text sizeWithFont:myLabel.font
constrainedToSize:myLabel.frame.size
lineBreakMode:UILineBreakModeWordWrap];
CGFloat labelHeight = labelSize.height;
int lines = [myLabel.text sizeWithFont:myLabel.font
constrainedToSize:myLabel.frame.size
lineBreakMode:UILineBreakModeWordWrap].height/16;
// '16' is font size
var size = myLabel.StringSize("Some really long string", myLabel.Font, myLabel.Frame.Size, UILineBreakMode.CharacterWrap);
var lines = size.Height / myLabel.Font.CapHeight;

iOS - sizeWithFont issue

I have text "Testing of application". it looks line in cell is
"Tesing of
Application"
I want to add a Asteric right a text. for e.g
"Tesing of *
Application"
but it show after the width of Label.. I have written the following code.
CGSize maximumLabelSize = CGSizeMake(99999,9999);
CGSize textSize = [[_label text] sizeWithFont:_label.font
constrainedToSize:maximumLabelSize lineBreakMode:_label.lineBreakMode];
float startX = 2 + textSize.width;
label2.frame = CGRectMake(startX, label2.frame.origin.y, label2.frame.size.width, label2.frame.size.height);
When you use constrainedToSize you have to pass the size with real height or real width.
Let say you have a label on 300px width and you want to make the height according to text length you have to use CGSizeMake(300,9999). In this case the constrainedToSizemethod will return you the height need to draw that text.
For getting the width you can set a small height like 20px to get the width needed to draw the text.

Getting the bounds of a NSString printed out with drawInRect

I am currently drawing text on string, using drawInRect, restricted to a size using CGRectSize. It is also centred.
[text drawInRect:CGRectMake(0,0,nWidth,nHeight) withFont:font lineBreakMode:UILineBreakModeWordWrap alignment:UITextAlignmentCenter];
What I need to find out is the size and offset of the actual drawn text.
Because it is centred, there is a very good chance that there is an X offset before text is drawn.
I've seen the command boundingRectWithSize but I think it may not be supported on IOS?
Get the size of the text,
CGSize size = [text sizeWithFont:font constrainedToSize:CGRectMake(0,0,nWidth,nHeight) lineBreakMode: UILineBreakModeWordWrap];
(See documentation)
Then, since it is centered, the bounds will be
CGRect bounds = CGRectMake((nWidth - size.width)/2, ..., size.width, size.height);
You want the method from the UIKit category on NSString...
- (CGSize)sizeWithFont:(UIFont *)font
which gives you the size needed, and then you can compute your centering (or other) positions.

iOS/UIFont - reducing font width

I have a UILabel that is a fixed size. Unfortunately on rare occasions, the text I need to fit into it doesn't fit! I have tried reducing the font size, but it needs to reduce so much that it looks terrible.
Is it possible to change the font width somehow? UIFont does not seem to have any properties to allow me to do this? Do I need to use a UIWebView and use CSS? I don't know much CSS, so any help is much appreciated if this is the best way to solve this.
Alternatively, any other ways to solve this?
Thanks Craig
The simplest way to shrink just the width of the text is to apply a transform to the label's layer:
label.layer.transform = CATransform3DMakeScale(desiredWidth/textWidth, 1.0, 1.0);
Do you mean you want to squeeze it horizontally while keeping the height? This is achievable, up to about 60% of the regular width. Beyond that it looks terrible.
Here is the drawRect for a UILabel subclass which squeezes independently on either axis if necessary.
// This drawRect for a UILabel subclass reproduces most common UILabel formatting, but does not do truncation, line breaks, or scaling to fit.
// Instead, it identifies cases where the label text is too large on either axis, and shrinks along that axis.
// For small adjustments, this can keep text readable. In extreme cases, it will create an ugly opaque block.
- (void) drawRect:(CGRect)rect;
{
CGRect bounds = [self bounds];
NSString *text = [self text];
UIFont *font = [self font];
// Find the space needed for all the text.
CGSize textSize = [text sizeWithFont:font];
// topLeft is the point from which the text will be drawn. It may have to move due to compensate for scaling, or due to the chosen alignment.
CGPoint topLeft = bounds.origin;
// Default to no scaling.
CGFloat scaleX = 1.0;
CGFloat scaleY = 1.0;
// If the text is too wide for its space, reduce it.
// Remove the second half of this AND statement to have text scale WIDER than normal to fill the space. Useless in most cases, but can be amusing.
if ((textSize.width>0) && (bounds.size.width/textSize.width<1))
{
scaleX = bounds.size.width/textSize.width;
topLeft.x /= scaleX;
}
else
{
// Alignment only matters if the label text doesn't already fill the space available.
switch ([self textAlignment])
{
case UITextAlignmentLeft :
{
topLeft.x = bounds.origin.x;
}
break;
case UITextAlignmentCenter :
{
topLeft.x = bounds.origin.x+(bounds.size.width-textSize.width)/2;
}
break;
case UITextAlignmentRight :
{
topLeft.x = bounds.origin.x+bounds.size.width-textSize.width;
}
break;
}
}
// Also adjust the height if necessary.
if ((textSize.height>0) && (bounds.size.height/textSize.height<1))
{
scaleY = bounds.size.height/textSize.height;
topLeft.y /= scaleY;
}
else
{
// If the label does not fill the height, center it vertically.
// A common feature request is for labels that do top or bottom alignment. If this is needed, add a property for vertical alignment, and obey it here.
topLeft.y = bounds.origin.y+(bounds.size.height-textSize.height)/2;
}
// Having calculated the transformations needed, apply them here.
// All drawing that follows will be scaled.
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextScaleCTM(context, scaleX, scaleY);
// Begin drawing.
// UILabels may have a shadow.
if ([self shadowColor])
{
[[self shadowColor] set];
CGPoint shadowTopLeft = CGPointMake(topLeft.x+[self shadowOffset].width/scaleX, topLeft.y+[self shadowOffset].height/scaleY);
[text drawAtPoint:shadowTopLeft withFont:font];
}
// The text color may change with highlighting.
UIColor *currentTextColor;
if ((![self isHighlighted]) || (![self highlightedTextColor]))
currentTextColor = [self textColor];
else
currentTextColor = [self highlightedTextColor];
// Finally, draw the regular text.
if (currentTextColor)
{
[currentTextColor set];
[text drawAtPoint:topLeft withFont:font];
}
}
You can set the minimum font size of a UILabel to a smaller value, and check Autoshrink to let it automatically shrink. This parameter is available in Interface Builder.
The internal implementation will reduce kerning, which is the width of space between characters. It cannot actually reduce width though.
This is your better bet. If you are still unsatisfied with results. You may have to change your design.

ios unread message icon

I was wondering if there is a standard method in iOS to produce the numbered bubble icon for unread messages as the ones used in mail for iphone and mac.
I'm not talking about the red dots on the application item which is done with badgevalue but about the blue bubble beside the mailboxes.
Of course one can do it manually using coregraphics but it's harder to match the dimensions and color of the standard ones used in mail etc.
here are three ways to do this, in order of difficulty..
screen shot your mail app from your iphone, send the image into photoshop, extract the blue dot and use it as an image in your app. To use it in a tableviewcell, you just set the imageView.image = [UIImage imageName:#"blueDot.png"];
same as #1, except save the image as a grayscale, this way you can use Quartz and overlay your own colors on top of it. so you can make that dot any color you want. Very cool stuff.
Use Quartz to draw the whole thing. Its really not that hard. Let me know if you would like some code for that.
OK, twist my arm... here is the code to draw your own gradient sphere... from quartz.
Make a class that inherits from UIView. add the following code
static float RADIANS_PER_DEGREE=0.0174532925;
-(void) drawInContext:(CGContextRef) context
{
// Drawing code
CGFloat radius = self.frame.size.width/2;
CGFloat start = 0 * RADIANS_PER_DEGREE;
CGFloat end = 360 * RADIANS_PER_DEGREE;
CGPoint startPoint = CGPointMake(0, 0);
CGPoint endPoint = CGPointMake(0, self.bounds.size.height);
//define our grayscale gradient.. we will add color later
CGFloat cc[] =
{
.70,.7,.7,1, //r,g,b,a of color1, as a percentage of full on.
.4,.4,.4,1, //r,g,b,a of color2, as a percentage of full on.
};
//set up our gradient
CGGradientRef gradient;
CGColorSpaceRef rgb = CGColorSpaceCreateDeviceRGB();
gradient = CGGradientCreateWithColorComponents(rgb, cc, NULL, sizeof(cc)/(sizeof(cc[0])*4));
CGColorSpaceRelease(rgb);
//draw the gray gradient on the sphere
CGContextSaveGState(context);
CGContextBeginPath(context);
CGContextAddArc(context, self.bounds.size.width/2, self.bounds.size.height/2, radius,start,end , 0);
CGContextClosePath(context);
CGContextClip(context);
CGContextAddRect(context, self.bounds);
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, kCGGradientDrawsBeforeStartLocation);
CGGradientRelease(gradient);
//now add our primary color. you could refactor this to draw this from a color property
UIColor *color = [UIColor blueColor];
[color setFill];
CGContextSetBlendMode(context, kCGBlendModeColor); // play with the blend mode for difference looks
CGContextAddRect(context, self.bounds); //just add a rect as we are clipped to a sphere
CGContextFillPath(context);
CGContextRestoreGState(context);
}
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
[self drawInContext:context];
}
If you want to use a graphic resource from iOS, you can find it using the UIKit-Artwork-Extractor tool. Extract everything to the desktop and find the one you want. For example, the red badge for notifications is called SBBadgeBG.png. I don't know which one you mean, so search for it yourself :P
This is what I did to use a badge, the procedure is exactly the same to show a bubble in a subview of your table:
// Badge is an image with 14+1+14 pixels width and 15+1+15 pixels height.
// Setting the caps to 14 and 15 preserves the original size of the sides, so only the pixel in the middle is stretched.
UIImage *image = [UIImage imageNamed:#"badge"];
self.badgeImage = [image stretchableImageWithLeftCapWidth:(image.size.width-1)/2 topCapHeight:(image.size.height-1)/2];
// what size do we need to show 3 digits using the given font?
self.badgeFont = [UIFont fontWithName:#"Helvetica-Bold" size:13.0];
CGSize maxStringSize = [[NSString stringWithString:#"999"] sizeWithFont:self.badgeFont];
// set the annotation frame to the max needed size
self.frame = CGRectMake(0,0,
self.badgeImage.size.width + maxStringSize.width,
self.badgeImage.size.height + maxStringSize.height);
and then override the method drawRect: of your view to paint the badge and the numbers inside:
- (void)drawRect:(CGRect)rect {
// get the string to show and calculate its size
NSString *string = [NSString stringWithFormat:#"%d",self.badgeNumber];
CGSize stringSize = [string sizeWithFont:self.badgeFont];
// paint the image after stretching it enough to acommodate the string
CGSize stretchedSize = CGSizeMake(self.badgeImage.size.width + stringSize.width,
self.badgeImage.size.height);
// -20% lets the text go into the arc of the bubble. There is a weird visual effect without abs.
stretchedSize.width -= abs(stretchedSize.width *.20);
[self.badgeImage drawInRect:CGRectMake(0, 0,
stretchedSize.width,
stretchedSize.height)];
// color of unread messages
[[UIColor yellowColor] set];
// x is the center of the image minus half the width of the string.
// Same thing for y, but 3 pixels less because the image is a bubble plus a 6px shadow underneath.
float height = stretchedSize.height/2 - stringSize.height/2 - 3;
height -= abs(height*.1);
CGRect stringRect = CGRectMake(stretchedSize.width/2 - stringSize.width/2,
height,
stringSize.width,
stringSize.height);
[string drawInRect:stringRect withFont:badgeFont];
}