9

Given a rectangular area, I want to render some text using a specific font and have the rendered text fill out the rectangle. As in the image below:

enter image description here

  1. This is not the same as just changing font size
  2. Rendering it as a bitmap and then scale it is not an option (it looks horrible)
  3. Vector graphics is the way to do it

Solution

I came up with the following which seems to work for my purposes. The code draws a single line of text scaling to fill the bounds. Subclass UIView and replace drawRect as follows.

- (void)drawRect:(CGRect)rect 
{
    [self drawScaledString:@"Abcde"]; 
}

- (void)drawScaledString:(NSString *)string
{
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetTextMatrix(context, CGAffineTransformIdentity);

    NSAttributedString *attrString = [self generateAttributedString:string];

    CFAttributedStringSetAttribute((CFMutableAttributedStringRef)attrString, CFRangeMake(0, string.length), 
                                   kCTForegroundColorAttributeName, [UIColor redColor].CGColor);

    CTLineRef line = CTLineCreateWithAttributedString((CFAttributedStringRef) attrString);

    // CTLineGetTypographicBounds doesn't give correct values, 
    // using GetImageBounds instead
    CGRect imageBounds = CTLineGetImageBounds(line, context);
    CGFloat width = imageBounds.size.width;
    CGFloat height = imageBounds.size.height;

    CGFloat padding = 0;

    width += padding;
    height += padding;

    float sx = self.bounds.size.width / width;
    float sy = self.bounds.size.height / height;

    CGContextSetTextMatrix(context, CGAffineTransformIdentity);

    CGContextTranslateCTM(context, 1, self.bounds.size.height);
    CGContextScaleCTM(context, 1, -1);
    CGContextScaleCTM(context, sx, sy);

    CGContextSetTextPosition(context, -imageBounds.origin.x + padding/2, -imageBounds.origin.y + padding/2);

    CTLineDraw(line, context);
    CFRelease(line);
}

- (NSAttributedString *)generateAttributedString:(NSString *)string
{

    CTFontRef helv = CTFontCreateWithName(CFSTR("Helvetica-Bold"),20, NULL);
    CGColorRef color = [UIColor blackColor].CGColor;

    NSDictionary *attributesDict = [NSDictionary dictionaryWithObjectsAndKeys:
                                    (id)helv, (NSString *)kCTFontAttributeName,
                                    color, (NSString *)kCTForegroundColorAttributeName,
                                    nil];

    NSAttributedString *attrString = [[[NSMutableAttributedString alloc]
                                   initWithString:string
                                        attributes:attributesDict] autorelease];

    return attrString;
}

Example usage:

CGRect rect = CGRectMake(0, 0, 50, 280);
MyCTLabel *label = [[MyCTLabel alloc] initWithFrame:rect];
label.backgroundColor = [UIColor whiteColor]; 
[self addSubview:label];
Martin Wickman
  • 19,662
  • 12
  • 82
  • 106
  • the solution you've added here doesn't produce any visible rendered text for me. When I start out with a bare bones project, add CoreText (and QuartzCore for good measure) then create a UIView subclass called MyCTLabel (again, for good measure), then add that as a subview to my view controller's view, I get a skinny narrow white column with no text. increasing the view's rect size has no affect on the visibility of the text... any thoughts? – Greg Combs Oct 06 '11 at 17:44
  • Worked for me, tested on iOS 6.1 and 9.3, thanks! – schmidt9 Sep 28 '16 at 08:07
  • Hey @Martin did you fin any solution to this problem, until now I cannot find an example to do this. If you know please share an example code here.Thanks – Coder ACJHP Jan 21 '21 at 10:47

3 Answers3

5

You can set the UILabel transform property and scale the width:

[myLabel sizeToFit];
myLabel.transform = CGAffineTransformMakeScale(0.5, 1.0);
TomSwift
  • 39,369
  • 12
  • 121
  • 149
  • 1
    I tried that, but the result is the text being rendered and then scaled as a bitmap which is not good enough, unfortunately. – Martin Wickman Mar 10 '11 at 07:55
2

You could try CoreText. Get a CTFramesetter, calculate its rect, then calculate the affine transform necessary to compress that rect into the bounds you want and set that as the CTM. Then when you draw the text, it should stretch it appropriately at full quality.

Lily Ballard
  • 182,031
  • 33
  • 381
  • 347
  • 1
    I looked into the CT docs, but it wasn't obvious to me how to do what you say. Apples examples didn't help much. I did, however, manage to render stretched text in Quartz 2D, but that forces me to do layout management (text wrapping etc) manually. Do you have anything more to go on regarding using CT, a example or something? – Martin Wickman Mar 10 '11 at 08:05
  • @Martin: I don't know where to find a good example. You need to create a `CTFramesetterRef` object, which you can use to draw the text. You'll also need to flip the coordinate system before drawing the text, as CoreText operates with the opposite coordinate system as UIView. – Lily Ballard Mar 10 '11 at 22:40
  • I'm marking this as the correct answer. CT severly lacks docs and examples, but I got it working eventually, thanks to this answer. – Martin Wickman Mar 12 '11 at 14:11
0

you can also try with the UILabel's @property(nonatomic) BOOL adjustsFontSizeToFitWidth and @property minimumFontSize

Initially you can set the much higher value for font property and also initialize the minimumFontSize with a minimum font value.

Jhaliya - Praveen Sharma
  • 31,697
  • 9
  • 72
  • 76