Categories
Blog

Two awkward things about iPhone development

I’m currently in the process of porting The Galactic Aquarium to the iPhone now that the 360 version is done. While doing this, I’ve come across a couple of awkward points to do with UIKit on the iPhone, so I thought I’d share how I got around them.

The first is using a custom font with a UILabel. Although you can set the font property of a UILabel, the only fonts you can create are system fonts. What you have to do is subclass the UILabel class and override the – (void)drawTextInRect:(CGRect)rect method with your own implementation.

First things first though, you need to load in your font. Note: With this method, you need to distribute the .ttf file with your app. Make sure this is ok in the font’s license.

// Get the path to our custom font and create a data provider.
NSString *fontPath = [[NSBundle mainBundle] pathForResource: @"FontFilename" ofType:@"ttf"];
CGDataProviderRef fontDataProvider = CGDataProviderCreateWithFilename([fontPath UTF8String]);
// Create the font with the data provider, then release the data provider.
// Note: s_CustomFont is a static variable, not an instance variable
// so that it can be shared between all instances of the custom font UILabel class
s_CustomFont = CGFontCreateWithDataProvider(fontDataProvider);
CGDataProviderRelease(fontDataProvider);

Now that you’ve got the font loaded in, you need to draw your text with it. Unfortunately, you can’t just set the font then call [super drawTextInRect: rect]. This is because there are actually two different methods of drawing fonts in Quartz. CGContextShowText displays a string of text using the currently selected font. You select the font using CGContextSelectFont, but this function is only capable of selecting a built in system font. The other option is to use CGContextShowGlyphs and CGContextSetFont. Notice that’s SetFont and not SelectFont.

The SelectFont / ShowText combination uses the name of the font to look up the font data and the glyph mapping table in the system. The glyph mapping table is used by the font renderering routines to match up glyphs from the font with characters in your string. There’s no way to programatically set the glyph mapping table, which is why there are the two methods of drawing text.

When using the SetFont / ShowGlyphs method, you need to manually convert the characters in your string to glyph indices in the font. Fortunately, this generally works out pretty easily, which we’ll see later.

Now that we have our font loaded in, we can write our custom drawTextInRect function. So, first thing’s first, you need to set the font to use:

CGContextSetFont(UIGraphicsGetCurrentContext(), s_CustomFont);
// We need to tell the system which font size to use too
float FontSize = 18; // 18-pt font size
CGContextSetFontSize(UIGraphicsGetCurrentContext(), FontSize);

Then we need to convert our string to glyph indices. Like I said before, this mostly works out quite easily. Usually, you just need to offset the character index by a “magic number”, and most of the time this number turns out to be -29:

// Convert the text to glyphs
int StringLength = [self.text length];
// Make sure you remember to free these after rendering
CGGlyph* Glyphs = malloc(sizeof(CGGlyph) * StringLength);
char* Chars = malloc(sizeof(char) * (StringLength + 1));
// Convert characters to glyph indices
[self.text getCString: Chars maxLength: (StringLength + 1) encoding: NSASCIIStringEncoding];
for(int CurrentChar = 0; CurrentChar < StringLength; ++CurrentChar)
{
    Glyphs[CurrentChar] = Chars[CurrentChar] - 29;
}

One thing to consider here is that any new line escape sequences will end up with some really high glyph index (as CGGlyph is just an unsigned short). But you have to handle new lines manually anyway, so it shouldn’t be too bad.

Finally, we can draw our text. You need to setup your text matrix and text position before drawing the text, like this:

// Set the text matrix, otherwise it draws upside down
CGAffineTransform TextMatrix = CGAffineTransformIdentity;
TextMatrix.d = -1;
CGContextSetTextMatrix(UIGraphicsGetCurrentContext(), TextMatrix);
// Set the text position - Remember, 0, 0 is the top left of the text's draw rectangle, not the screen
CGContextSetTextPosition(UIGraphicsGetCurrentContext(), 0, 0);

Note that CGContextSetTextPosition sets the same translation values that the translation component of the text matrix sets, so in the above case it’s obsolete. But when you start adding multi-line and alignment support, it’s cleaner to just set the text matrix once and then just use CGContextSetTextPosition to set the position.

Now just draw your glyphs:

CGContextShowGlyphs(UIGraphicsGetCurrentContext(), Glyphs, StringLength);

So there’s our basic custom font drawing routing. If I recall correctly, it’ll crash as soon as you have a newline in your string, because it tries to just use a glyph that isn’t there. What you have to do is, during renderering, scan along your glyphs until there is a new line, draw the glyphs up until the new line, then start again from just after the new line.

You can also implement alignment and word wrapping. To do this, you need to be able to measure the string, so here’s how you do that:

After a call to CGContextShowGlyphs, the current text position is updated and can be retrieved like this:

CGPoint EndPoint = CGContextGetTextPosition(UIGraphicsGetCurrentContext());

So EndPoint.x is the length of your string (assuming your initial text position was at 0, 0). You can prevent Quartz from actually doing any drawing while you’re measuring the string’s length by setting the text drawing mode to invisible:

CGContextSetTextDrawingMode(UIGraphicsGetCurrentContext(), kCGTextInvisible);

Remember to set your drawing mode and text position back to normal when you want to actually draw your text:

CGContextSetTextDrawingMode(UIGraphicsGetCurrentContext(), kCGTextFill);
// You can also set to the text colour to the UILabel's textColor property
CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), self.textColor.CGColor);

Now that you have the text size, you can use the UILabel’s textAlignment property to determine how to align your text when you actually draw it. Word wrapping can be achieved using a standard word wrapping algorithm. Just keep drawing glyphs until EndPoint.x is greater than self.frame.size.width. Then you know you’ve exceeded the frame and need to word wrap. So then you can jump back to the previous word, reset the text position to 0, 0 and continue on. It’s best to store each line length during your measuring stage so that they can be used to align the text during drawing.

One problem I haven’t yet figured out is the height of a line. At the moment I’m just using FontSize * 1.2, which seems to work OK for the font I’ve tried this on. I’m sure there’s probably some better way to do it, but I haven’t looked into it too much.

You can use the measuring technique to automatically size the height of your custom UILabel, to prevent it from being clipped (seeing as how you can’t actually see your custom font in Interface Builder). I added a new function to my custom font label class to calculate the height of the word wrapped text. I then use the height to set the height of the frame. This does mean that I have to set the frame’s width, call the function then set the frame’s height to the returned value. It also means you have to make sure the graphics context is correctly set up during the heigh measuring function (which it probably won’t be if it’s called from outside the drawTextInRect function. The easiest way to do this is to create a temporary graphics context, like this:

CGSize ContextSize;
ContextSize.width = 480;
ContextSize.height = 320;
UIGraphicsBeginImageContext(ContextSize);
// Measure the string's height
.....
UIGraphicsEndImageContext();

So there you have it. That’s how I’ve got my custom font UILabels to be used.

The other awkward thing is a lot simpler. I didn’t realise how long that last one was going to be 😀

The problem arises when you want to display a modal view controller straight after another one has been dismissed. It seems the best place to show the second one is in the viewDidDisappear function of the first UIViewController. However, calling presentModalViewController on the parent UIViewController from this function doesn’t work (nothing seems to happen). I’m guessing it’s because the modal view controller for the parent view controller is still pointing to the one that’s just disappeared, and that this gets cleaned up after viewDidDisappear is called, so any new modal view controller you set in viewDidDisappear is reset straight after the function returns (because the parent view controller is still in the process of cleaning up the first one). The solution is to use the performSelector function on the parent view controller with a delay of 0:

[self.parentViewController performSelector: @selector(myDisplayNextModalViewControllerFunction) withObject: nil afterDelay: 0];

The delay of 0 doesn’t mean that the selector is performed straight away. Because of the order of events in the run loop, the selector will be performed after the parent view controller has fully dismissed its modal view controller, so the next one will display fine.

Hope this helps 🙂

6 replies on “Two awkward things about iPhone development”

Great post! I am reasonably comfortable with UIKit but looking at low-level stuff like custom drawing routines makes me nervous. 🙂
Long time since this post was originally put up, but I will chip with some of my thoughts anyway:
“….I’m guessing it’s because the modal view controller for the parent view controller is still pointing to the one that’s just disappeared, and that this gets cleaned up after viewDidDisappear is called, so any new modal view controller you set in viewDidDisappear is reset straight after the function returns (because the parent view controller is still in the process of cleaning up the first one)…”
Not sure if that is entirely true. I have called ‘presentModalViewController” on the parent even BEFORE viewDidDisappear and that has worked. With one modal sliding in and another sliding out at the same time, it didn’t look pretty. But it worked.
One reason why the call from viewDidDisappear of the first modal might not have worked could be that the view is released as soon as it disappears. Posing a counter-point to myself, I would imagine that the API would call viewDidDisappear before removing the modal from the window and then releasing it. But the only way to confirm/reject either theory would be to check with some NSLogs in viewWill and viewDidDisappear.

Thanks! As you say, it is a long time since the post was put up, and Apple may have change things internally / fixed bugs that were there since then, so certain things in there may or may not be relevant 🙂

Thanks for the post I was about to write a whole ton of code to solve a modal to modal transition problem…

Hi,
thanks for this nice example. but I have trouble reading Cstrings with a Umlaut and converting them into char / glyphs.
I tried all sorts of encodings but I didnt get it to work. do you have any hints for me?
cheers
Tom

Cheers mate!!! (had a look at it once and didnt get it to work for some reason.. but you made me looking at it again and I found what I was previously missing..)
Thank you heaps!
keep on the good work!
peace
Tom

Comments are closed.