acb's technical journal

Automatically sizing CATextLayer to fit

When developing on iOS, I am a big fan of CoreAnimation, and particularly the CALayer and its subclasses; they're lighter in weight than UIView, carrying less baggage, and are a quick way to display all sorts of elements in a user interface.

CALayer has a number of useful subclasses, among them CATextLayer, which displays text in an arbitrary font and size* within its bounds. One shortcoming of CATextLayer, however, is that it does not provide any means of measuring the dimensions of its contents, requiring you to guesstimate them and size the layer to fit, or else pre-measure the string before sizing your layer. This is complicated somewhat by the fact that CATextLayer can accept font settings in several forms (string font name, CoreText CTFont or CoreGraphics CGFont), none of which are the convenient UIFont object.

Having had reason to create a precisely-sized CATextLayer recently, I looked into the issue and have written a small category extending CATextLayer, adding a method for setting its bounds to neatly encompass its text.

The interface file, CATextLayer+AutoSizing.h, looks as follows, and simply adds the adjustBoundsToFit method:

//  CATextLayer+AutoSizing.h
//
//  Created by Andrew Bulhak on 02/08/2013.
//  This code is placed in the public domain`

#import <QuartzCore/QuartzCore.h>

@interface CATextLayer (AutoSizing)
- (void) adjustBoundsToFit;

@end
The implementation of this, in CATextLayer+AutoSizing.m, looks like:
//  CATextLayer+AutoSizing.m
//
//  Created by Andrew Bulhak on 02/08/2013.
//  This code is placed in the public domain`

#import "CATextLayer+AutoSizing.h"
#import <UIKit/UIKit.h>
#import <CoreText/CoreText.h>

@implementation CATextLayer (AutoSizing)

- (void) adjustBoundsToFit {

    NSAttributedString *as;
    if([self.string isKindOfClass:[NSAttributedString class]]) {
        as = self.string;
#if ! __has_feature(objc_arc)
        [as retain];
#endif
    } else {
        
        UIFont* outfont;
        CFTypeRef layerfont = self.font;
        
        if(layerfont && [(__bridge id) layerfont isKindOfClass:[NSString class]]) {
            outfont = [UIFont fontWithName:(__bridge NSString*)layerfont size:self.fontSize];
        } else {
            CFTypeID ftypeid = CFGetTypeID(layerfont);
            if(ftypeid == CTFontGetTypeID()) {
                CFStringRef fname = CTFontCopyPostScriptName(layerfont);
                outfont = [UIFont fontWithName:(__bridge NSString*)fname size:self.fontSize];
                CFRelease(fname);
            } 
            else if (ftypeid == CGFontGetTypeID()) {
                CFStringRef fname = CGFontCopyPostScriptName(layerfont);
                outfont = [UIFont fontWithName:(__bridge NSString*)fname size:self.fontSize];
                CFRelease(fname);
                
            }
            else { // It's undefined, and defaults to Helvetica
                outfont = [UIFont systemFontOfSize:self.fontSize];
            }
        }
    
    as = [[NSAttributedString alloc] initWithString:self.string attributes:@{NSFontAttributeName: outfont}];
    }
    
    CGRect f = self.frame;
    f.size = [as size];
    self.frame = f;
    
#if ! __has_feature(objc_arc)
    [as release];
#endif
}

@end
This code works by converting the CATextLayer's text into an attributed string (assuming that it isn't one already), with the appropriate font, and using NSAttributedString's size method to measure it. Most of the fiddly work is in determining the font the layer is to be rendered with and creating a UIFont matching that. (Interestingly enough, even if the font is a CTFont, with a font size baked in, that size is ignored in favour of the layer's fontSize property.)

Anyway, hopefully this code will save someone else a few hours of fiddling around.


* or, using NSAttributedString, a combination of fonts and sizes, but this is beyond the scope of this post.

There are no comments yet on "Automatically sizing CATextLayer to fit"

Want to say something? Do so here.

Post pseudonymously

Display name:
URL:(optional)
To prove that you are not a bot,
please enter the text in the image on the right
in the field below it.

Your Comment:

Please keep comments on topic and to the point. Inappropriate comments may be deleted.

Note that markup is stripped from comments; URLs will be automatically converted into links.