January 30, 2009

iPhone: Custom font loading : complete example

UPDATE: It looks like someone has built a far more robust custom font engine than my example here. I haven't tried it yet, but the example project looks great: http://github.com/zynga/FontLabel

This code was cobbled together from a couple different posts I found on blogs and iphone forums, here and here. This is a complete example, which nobody wanted to supply - I hope this is useful to someone. The one major hole is that is does not support multi-line text. Let me know if you figure out a good way to handle it :)

I'm just going to post my 2 classes here. I've set up a base class that does the drawing, and a subclass that defines font-specific configuration. You'll have to put your .ttf (TrueType) font in the bundle, and use it's name there to customize your own font subclass. You'll also have to set the glyph offset in the subclass - a font-editing tool can help with this.

Here we go:

CustomFontBase.h
@interface CustomFontBase : UIView {
NSMutableString *curText;
UIColor *fontColor;
UIColor *bgColor;
int fontSize;
NSString *fontName;
NSString *fontExtension;
float autoSizeWidth;
int glyphOffset;
BOOL isGlowing;
UIColor *glowColor;
}

- (void)updateText:(NSString*)newText;
- (void)initTextWithSize:(float)size color:(UIColor*)color bgColor:(UIColor*)bgColor;
- (void)setGlow:(BOOL)glowing withColor:(UIColor*)color;
- (void)autoSizeWidthNow;

@property (nonatomic, retain) NSMutableString *curText;

@end

CustomFontBase.m
#import "CustomFontBase.h"


@implementation CustomFontBase

@synthesize curText;


- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// set defaults
[self setBackgroundColor:[UIColor clearColor]];
bgColor = [UIColor clearColor];
[self setCurText: [[NSMutableString alloc] initWithString:@""] ];
fontColor = [UIColor whiteColor];
fontSize = 15;
isGlowing = FALSE;
[self setContentMode:UIViewContentModeTopLeft];  // make sure it doesn't scale/deform when setFrame is called 
}
return self;
}


- (void)drawRect:(CGRect)rect {
// get context and flip for normal coordinates
CGContextRef context =  UIGraphicsGetCurrentContext();
CGContextTranslateCTM ( context, 0, self.bounds.size.height );
CGContextScaleCTM ( context, 1.0, -1.0 );

// Get the path to our custom font and create a data provider.
NSString *fontPath = [[NSBundle mainBundle] pathForResource:fontName ofType:fontExtension];
CGDataProviderRef fontDataProvider = CGDataProviderCreateWithFilename([fontPath UTF8String]);
// Create the font with the data provider, then release the data provider.
CGFontRef customFont = CGFontCreateWithDataProvider(fontDataProvider);
CGDataProviderRelease(fontDataProvider); 
// Set the customFont to be the font used to draw.
CGContextSetFont(context, customFont);

// prepare characters for printing
NSString *theText = [NSString stringWithString: curText];
int length = [theText length];
unichar chars[length];
CGGlyph glyphs[length];
[theText getCharacters:chars range:NSMakeRange(0, length)];

// draw bg
if( bgColor != [UIColor clearColor] )
{
CGRect bgRect = CGRectMake (0, 0, self.bounds.size.width, self.bounds.size.height);
CGContextSetFillColorWithColor( context, bgColor.CGColor );
CGContextFillRect( context, bgRect );
}

// Set how the context draws the font, what color, how big.
CGContextSetTextDrawingMode(context, kCGTextFill);
CGContextSetFillColorWithColor(context, fontColor.CGColor );
CGContextSetFontSize(context, fontSize);

// set a glow?
if( isGlowing ) {
//CGContextSetShadow(context, CGSizeMake(0,0), 3 );
CGContextSetShadowWithColor( context, CGSizeMake(0,0), 3, glowColor.CGColor );
}

// Loop through the entire length of the text.
for (int i = 0; i < length; ++i) {
// Store each letter in a Glyph and subtract the MagicNumber to get appropriate value.
glyphs[i] = [theText characterAtIndex:i] + glyphOffset;
}

// draw the glyphs
CGContextShowGlyphsAtPoint( context, 0, 0 + fontSize * .25, glyphs, length ); // hack the y-point to make sure it's not cut off below font baseline - this creates a perfect vertical fit

// get width of text for autosizing the frame later (perhaps)
CGPoint textEnd = CGContextGetTextPosition( context ); 
autoSizeWidth = textEnd.x;

// clean up the font
CGFontRelease( customFont );
}


// call this after creating the LandscapeText object to set the styling
- (void)initTextWithSize:(float)size color:(UIColor*)color bgColor:(UIColor*)txtBgColor {
// store font properties
fontColor = color;
fontSize = size;
bgColor = txtBgColor;

// autoscale height to font size
[self setFrame:CGRectMake(self.frame.origin.x, self.frame.origin.y, self.frame.size.width, fontSize)];
}

// set new text to display
- (void)updateText:(NSString*)newText {
[self setCurText: [NSString stringWithString:newText] ];
[self setNeedsDisplay];
}

- (void)setGlow:(BOOL)glowing withColor:(UIColor*)color {
glowColor = color;
isGlowing = glowing;
}

- (void)autoSizeWidthNow {
//printf( "autoSizeWidth = %f \n", autoSizeWidth );
[self setFrame:CGRectMake(self.frame.origin.x, self.frame.origin.y, autoSizeWidth, fontSize)];
}


- (void)dealloc {
[curText release];
[super dealloc];
}


@end
CustomFontMyFont.h
#import 
#import "CustomFontBase.h"

@interface CustomFontMyFont : CustomFontBase {

}

@end
CustomFontMyFont.m
#import "CustomFontMyFont.h"


@implementation CustomFontMyFont


- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
// Initialization code
fontName = @"MyFont";
fontExtension = @"ttf";
glyphOffset = -29;        // adjust this offset per font until it prints the proper characters
}
return self;
}


- (void)dealloc {
[super dealloc];
}


@end
Usage:
CustomFontMyFont *myLabel = [[[CustomFontMyFont alloc] initWithFrame:CGRectMake(100, 100, 55, 20)] autorelease];
[myLabel initTextWithSize:11 color:[UIColor whiteColor] bgColor:[UIColor clearColor]];
[myLabel updateText:@"Custom Font"];
[self addSubview:myLabel];

30 comments:

  1. Hey,

    Thanks for the code :D

    How can I use this code in a UITableView/Cell and a UILabel?

    Thanks.

    ReplyDelete
  2. It's a UIView subclass, so use it just like any normal UIView.

    ReplyDelete
  3. Thanks Justin. Is there a proper way to use it? I'm tried doing it but it crashes. I'm trying make the text in a tableview and UILabel use that custom font, how can I do that?

    ReplyDelete
  4. I added to the bottom of my post, with the code to create a text field. you have to create the textfield with the code above. this does not work as a UILabel. I can't give much more advice than that.

    ReplyDelete
  5. Justin, thanks a lot! You saved my nerves )

    ReplyDelete
  6. How do you use the autoSizeWidthNow method?

    ReplyDelete
  7. Oh, and by the way: is it possible to let CG draw centered text?

    ReplyDelete
  8. You call the autoSizeWidthNow simply by sending that message to your new custom font text field:

    [myLabel autoSizeWidthNow];

    This will allow you to center or right-align the text as it adjusts the frame of your text field. The only problem is that you can't seem to create a text field and call the autoSizeWidthNow function on the same frame. I had to set up a timer to call autoSizeWidthNow a fraction of a second later, because objective-c doesn't seems to recognize resized UIView frame/bounds dimensions until the next frame. If anyone has any insight into that, I'd be interested to learn. It seems like there should be something like setNeedsDisplay, but for UIView elements, not just the CG drawing layer. Hope that helps explain my code!

    ReplyDelete
  9. Thanks for the post. It's easier to learn from
    code than from documentation.
    I'll return a couple of clues: that declaration
    for the glyphs array needs a constant at compile
    time, not a runtime variable for the array size;
    and that chars array is unused in your example.
    And if you open Font Book, click on Edit>>Special
    Charcters, and select your font, you'll see part
    of the reason for the glyphoffset variable.

    ReplyDelete
  10. That last portion of code that you added, where does it go? I got everything else done and it returns a blank screen. I assume that that last part is the part that actually does stuff. Would you consider emailing the files? If not, I understand but I have been searching for how to do this FOREVER and can't get it to work. Thanks!

    ReplyDelete
  11. the Usage code goes in any UIView subclass where you're building a view with code. You need to import your custom font class, then use the Usage code where you're building the view. All the code you need is provided here. take note that I don't use Interface Builder. everything I create is built entirely with code, so if you're using IB, I can't give good advice.

    ReplyDelete
  12. Would you mind looking at my code to see what is wrong. This still isn't working. The file can be found here: http://cdreich.com/MyCode.html

    Thank you sooo much for all of your help. I am sorry if I am a bother.

    ReplyDelete
  13. Thanks! This solve all of my problems :)

    ReplyDelete
  14. Thanks!

    I found a leaking object in CustomFontBase.m

    Replace:

    [self setCurText: [[NSMutableString alloc] initWithString:@""]];

    with:

    curText = [[NSMutableString alloc] initWithString:@""];

    and all should be good

    ReplyDelete
  15. Hey This class is great, seems to work fine, The only problem I have run into is that when you put the custom font inside of each cell of a table view, it will scroll down fine, but as soon as you scroll the table view up, it crashes with this error.

    NSInvalidArgumentException, [RootViewController updateText:] unrecognized selector sent to class 0x84a0

    any ideas? thanks!

    ReplyDelete
  16. This code works fine if you only need ASCII, no unicode or extended characters. For a unicode solution that's mostly similar except that it reads the font's cmap to find the glyphs, registered developers should consult this thread

    https://devforums.apple.com/message/113783

    ReplyDelete
  17. if i use in a for cycle:

    [myLabel updateText:[my_array objectAtIndex:i]];

    The label return not a string but a number

    myArray = [[NSArray alloc] initWithObjects:
    @"111",
    @"222",
    @"333",
    @"444",
    @"555",
    nil];

    Return
    001,002,003,004,005
    Why?
    Any solution?
    Thz.

    ReplyDelete
  18. So I wast using this code got most stuff to work, cept when I try to use say

    [myLabel initTextWithSize:11 color:[UIColor colorWithRed:...] bgColor:[UIColor colorWithRed:...]

    I'm not really sure why not? I've changed the CGSetContextFillWithColor method in the CustomFontMyFont class too specify a [UIColor ..].CGColor and that works, but don't know why I can't pass it through initWithText?

    ReplyDelete
  19. I am using the code exactly as presented, but getting a leak on this line in initWithFrame:

    [self setCurText: [[NSMutableString alloc] initWithString:@""] ];

    By doing "Build and Analyze" I get this report:
    "Potential leak of an object on line 26" (the setCurText line above is line 26)

    Anyone else see this? Any fixes that can correct it?

    Thanks!

    ReplyDelete
  20. I wanted to have custom fonts, cause I habe Chinese text and I wanted nice chinese signs (like when you draw with a brush).
    But it seems that it doesn't work with chinese fonts. Maybe you know why? I want to print this f.e.: ä½ 
    And I would like to use a font like HDZB_36.TTF (first result in google)

    ReplyDelete
  21. Justin, this is great. I am not a programmer but a font developer. Would you be interested to talk about bringing our fonts to iPhone? Please write me at peter at typotheque dot com

    ReplyDelete
  22. how could i use it for UIWebview's default font?

    ReplyDelete
  23. any good tool to find the glyh offset ?

    ReplyDelete
  24. Great helpful code!

    But I'm stuck trying to right align the text. Could anyone please point it out please????

    ReplyDelete
  25. It looks like someone has built a far more robust custom font engine than my example here. I haven't tried it yet, but the example project looks great: http://github.com/zynga/FontLabel

    Thanks to all who have used my code, and sorry for not replying to all the questions.

    ReplyDelete
  26. Hi Justin,

    I am trying to use above in project and just added above classes, and then added customfontmyfont view to a viewcontroller, and my controller is not showing it up.. your little help on this would be really appreciated.. thanks a ton..

    ReplyDelete
  27. Hi Justin ,

    i want to use custom fonts with UITextView .. Please help !

    Thanks
    Yasir

    ReplyDelete
  28. Great Classes very helpful, especially for portability.

    If people are loading the text to screen via viewDidLoad method use this:
    [self.view addSubview:myTitle];

    Instead of:
    [self addSubview:myTitle];

    Hope that helps some newbie programmers.

    Nome Skavinski.

    ReplyDelete