iPhone cert FUD
February 12, 2010 9:24 amI finally got around to installing Snow Leopard on my MacBook Pro. I don’t believe in putting a new operating system on top of an old one, so I started over from scratch, on a brand-new hard drive. Which explains why I waited so long. Installing everything from scratch is torture.
A couple of days ago, I could no longer postpone reinstalling all the dev certs and crud you need to make iPhone apps that you can put on real iPhone hardware. As I was installling all that stuff, I encountered this interesting text from Apple’s developer website, concerning development certificates:
It is critical that you save your private key somewhere safe in the event that you need to develop on multiple computers or decide to reinstall your system OS. Without your private key, you will be unable to sign binaries in Xcode and test your application on any Apple device. When a CSR is generated, the Keychain Access application creates a private key on your login keychain. This private key is tied to your user account and cannot be reproduced if lost due to an OS reinstall. If you plan to do development and testing on multiple systems, you will need to import your private key onto all of the systems you’ll be doing work on.
Well! From the sound of that, you would be forgiven for thinking that, if you don’t back up your private key, you’ll be completely dead in the water, never to write another iPhone app again. That’s certainly what I thought when I was setting this stuff up the first time, about a year ago. So I did just what they told me to, and I backed it all up. I was reading somebody else’s blog recently that claimed the text quoted above was literally true. Which is why I’m writing today.
Actually, no. If you don’t back up that stuff, you won’t be dead in the water. I found my cert backups from a year ago, and tried to use them, but it was just too painful. Nothing about Apple’s iPhone code signing process makes much sense. When you get stuck, it’s best to just blow everything away and start over.
Which is just what I did! I removed everything from my keychain that had anything to do with iPhone development, I threw away all my provisioning profiles, and I started over from scratch. It went a lot easier this time. I think I’ve finally been doing this long enough to understand a few of the concepts.
From the sound of it, I think the only time you’d really need to follow Apple’s backup device is if you plan to use two or more Macs for iPhone development simultaneously. I’ve never needed to do that, so that’s one less thing to worry about.
Categories: iphone, itunes app store, programming
Comments Off
hearts progress
February 11, 2010 9:04 amI’m about to release new versions of both my hearts games. I plan to add a link on the help tab that will show people the latest blog entries pertaining to my card games. I guess that means that I should have at least one blog entry to link to, which is why I am writing this. Heh!
As of this writing, Hearts Net has been in the app store for 12 days. So far I’ve made $64.11 on it. Not exactly getting rich, but I’m still twiddling the knobs. Yesterday was my most profitable day yet, so the situation could still pick up. There’s a lot of stuff I can try.
My best beta tester to date discovered that wifi games won’t work in environments with restrictive firewalls. (Thanks, Philip!) I know what it will take to fix it, which I plan to roll out in an update. If anybody else has run into this problem, please speak up. If it’s affecting a lot of people, that would make it a higher priority.
Personally, I think it’s more important to port the game to the Mac. Apple’s App Store policies have conspired to convince people that iPhone apps should be $0.99 or free, so that’s working against me. Mac users have a reputation for paying for software they use, so I think I’d rather be in that market.
Yes, I know that almost all of you are clamoring for configurable rule variations and better Hearts robots. I’ll get there, but I can’t do all this for free. If the Mac version turns out to be more profitable, then I’ll be more in the mood to do all the stuff you guys want me to.
Categories: cards
Comments Off
willow weep for me
February 2, 2010 10:53 amLast year I went through a whole bunch of different ideas for iPhone apps I might write. I dawdled for way too long, spent too much time working on apps that I ultimately scrapped, and generally wasn’t very efficient with my time. But I finally got something into the app store in June of last year. I made it free, because I wasn’t sure how it would go over. Taking people’s money is Serious Business.
I released a card game that I call Hearts Solo. It has been pretty popular. To date, it has been downloaded by 27,942 people. Of that number, 5,536 upgraded to version 1.1.0 when it was released. So I felt that I had adequately paved the way for a better version that I could charge real money for.
Once again, I took way too long with the follow-up. Adding network play turned out to be pretty close to the hardest programming challenge I’ve ever attempted. It sounds easy enough, especially since I’ve done so much network programming in the past, so I can’t explain it. My best guess is that I didn’t choose a very good internal structure for the code. The practical upshot is that it took me about six months to finish.
I put Hearts Net on the App Store on Friday the 29th at $3.99. Two people got the game with promo codes I handed out. A third person bought it with actual money, but I happen to know that it was because she doesn’t know how promo codes work and screwed up, because she emailed me about it. So I lowered the price to $1.99, and the next day I got six sales, none with promo codes. So I figured, hey, things are looking up. The next day, five sales. Yeah, this is not looking good. So I made the game free, hoping that will help get the word out, and I can start charging later.
Yesterday was the first day that the game was free. Total downloads: 1,717. My best day before this was when I released version 1.1.0 of Hearts Solo. It was downloaded 590 times on July 31.
SO. Here’s what this says to me. There is an awareness of my game among the people who might want to play it, and a fair amount of demand. Quite a few of those people are playing it. I gave them a free version to start, so they know what they’re getting into. But they’re not willing to pay even $1.99 for it?
Come on. Really? Not even $1.99? You’re breaking my heart here.
Categories: itunes app store
Comments Off
iPhone: UIImage rotation and scaling
January 31, 2010 8:03 pmHello to my three remaining blog subscribers! Long time no see!
For my first post back after my long hiatus, I’m going to revisit the single most popular entry I’ve written to date, which was about UIImage rotation. I’ve used that code a great deal since I first wrote it. I’ve modified it several times. It’s pretty near perfect at this point.
The original code I modified for that earlier post was for dealing with photos taken with the iPhone camera. I stripped out a lot of that stuff, because I was only interested in rotating images that were embedded in the program. And then, wouldn’t you know it, I got a contract job that required me to deal with iPhone camera images as well. So I had to revisit the subject.
The original code from blog.logichigh.com had the rotation and scaling all lumped together in one chaotic function. I split it out into separate rotation and scaling methods, so they can be used independently. It also makes for easier code maintenance.
The code contains [UIImage rotate:], which works the same as the last time around, but it has been streamlined a bit internally. It will rotate any UIImage to any orientation, with or without mirroring. Then we have two scaling methods, the simplest being [UIImage scaleWithMaxSize:]. You provide it with a float value, which is the largest width and/or height that you want the output image to have. If the input image is already smaller than that, it won’t be scaled.
Finally, the all-singing, all-dancing method for massaging photos from the iPhone camera: [UIImage rotateAndScaleFromCameraWithMaxSize:]. It examines the EXIF data in the image and, if needed, rotates it to the proper orientation. Then it scales the image to the maximum width and/or height supplied to the method.
This code has been thoroughly tested in several real-world iPhone projects. There are no known bugs. If you find one, I’d love to hear about it, so I can fix it.
Here’s the header file, WBImage.h:
// WBImage.h -- extra UIImage methods // by allen brunson march 29 2009 #ifndef WBIMAGE_H #define WBIMAGE_H #import <UIKit/UIKit.h> @interface UIImage (WBImage) // rotate UIImage to any angle -(UIImage*)rotate:(UIImageOrientation)orient; // rotate and scale image from iphone camera -(UIImage*)rotateAndScaleFromCameraWithMaxSize:(CGFloat)maxSize; // scale this image to a given maximum width and height -(UIImage*)scaleWithMaxSize:(CGFloat)maxSize; -(UIImage*)scaleWithMaxSize:(CGFloat)maxSize quality:(CGInterpolationQuality)quality; @end #endif // WBIMAGE_H
And here’s the implementation file, WBImage.mm:
// WBImage.mm -- extra UIImage methods // by allen brunson march 29 2009 #include "WBImage.h" static inline CGFloat degreesToRadians(CGFloat degrees) { return M_PI * (degrees / 180.0); } static inline CGSize swapWidthAndHeight(CGSize size) { CGFloat swap = size.width; size.width = size.height; size.height = swap; return size; } @implementation UIImage (WBImage) // rotate an image to any 90-degree orientation, with or without mirroring. // original code by kevin lohman, heavily modified by yours truly. // http://blog.logichigh.com/2008/06/05/uiimage-fix/ -(UIImage*)rotate:(UIImageOrientation)orient { CGRect bnds = CGRectZero; UIImage* copy = nil; CGContextRef ctxt = nil; CGRect rect = CGRectZero; CGAffineTransform tran = CGAffineTransformIdentity; bnds.size = self.size; rect.size = self.size; switch (orient) { case UIImageOrientationUp: return self; case UIImageOrientationUpMirrored: tran = CGAffineTransformMakeTranslation(rect.size.width, 0.0); tran = CGAffineTransformScale(tran, -1.0, 1.0); break; case UIImageOrientationDown: tran = CGAffineTransformMakeTranslation(rect.size.width, rect.size.height); tran = CGAffineTransformRotate(tran, degreesToRadians(180.0)); break; case UIImageOrientationDownMirrored: tran = CGAffineTransformMakeTranslation(0.0, rect.size.height); tran = CGAffineTransformScale(tran, 1.0, -1.0); break; case UIImageOrientationLeft: bnds.size = swapWidthAndHeight(bnds.size); tran = CGAffineTransformMakeTranslation(0.0, rect.size.width); tran = CGAffineTransformRotate(tran, degreesToRadians(-90.0)); break; case UIImageOrientationLeftMirrored: bnds.size = swapWidthAndHeight(bnds.size); tran = CGAffineTransformMakeTranslation(rect.size.height, rect.size.width); tran = CGAffineTransformScale(tran, -1.0, 1.0); tran = CGAffineTransformRotate(tran, degreesToRadians(-90.0)); break; case UIImageOrientationRight: bnds.size = swapWidthAndHeight(bnds.size); tran = CGAffineTransformMakeTranslation(rect.size.height, 0.0); tran = CGAffineTransformRotate(tran, degreesToRadians(90.0)); break; case UIImageOrientationRightMirrored: bnds.size = swapWidthAndHeight(bnds.size); tran = CGAffineTransformMakeScale(-1.0, 1.0); tran = CGAffineTransformRotate(tran, degreesToRadians(90.0)); break; default: // orientation value supplied is invalid assert(false); return nil; } UIGraphicsBeginImageContext(bnds.size); ctxt = UIGraphicsGetCurrentContext(); switch (orient) { case UIImageOrientationLeft: case UIImageOrientationLeftMirrored: case UIImageOrientationRight: case UIImageOrientationRightMirrored: CGContextScaleCTM(ctxt, -1.0, 1.0); CGContextTranslateCTM(ctxt, -rect.size.height, 0.0); break; default: CGContextScaleCTM(ctxt, 1.0, -1.0); CGContextTranslateCTM(ctxt, 0.0, -rect.size.height); break; } CGContextConcatCTM(ctxt, tran); CGContextDrawImage(ctxt, rect, self.CGImage); copy = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return copy; } -(UIImage*)rotateAndScaleFromCameraWithMaxSize:(CGFloat)maxSize { UIImage* imag = self; imag = [imag rotate:imag.imageOrientation]; imag = [imag scaleWithMaxSize:maxSize]; return imag; } -(UIImage*)scaleWithMaxSize:(CGFloat)maxSize { return [self scaleWithMaxSize:maxSize quality:kCGInterpolationHigh]; } -(UIImage*)scaleWithMaxSize:(CGFloat)maxSize quality:(CGInterpolationQuality)quality { CGRect bnds = CGRectZero; UIImage* copy = nil; CGContextRef ctxt = nil; CGRect orig = CGRectZero; CGFloat rtio = 0.0; CGFloat scal = 1.0; bnds.size = self.size; orig.size = self.size; rtio = orig.size.width / orig.size.height; if ((orig.size.width <= maxSize) && (orig.size.height <= maxSize)) { return self; } if (rtio > 1.0) { bnds.size.width = maxSize; bnds.size.height = maxSize / rtio; } else { bnds.size.width = maxSize * rtio; bnds.size.height = maxSize; } UIGraphicsBeginImageContext(bnds.size); ctxt = UIGraphicsGetCurrentContext(); scal = bnds.size.width / orig.size.width; CGContextSetInterpolationQuality(ctxt, quality); CGContextScaleCTM(ctxt, scal, -scal); CGContextTranslateCTM(ctxt, 0.0, -orig.size.height); CGContextDrawImage(ctxt, orig, self.CGImage); copy = UIGraphicsGetImageFromCurrentImageContext(); UIGraphicsEndImageContext(); return copy; } @end
Categories: cocoa, iphone, objective-c, programming
Comments Off
iPhone: keyboard trouble
August 28, 2009 3:07 am
It is possible to type into two or more of these fields without dismissing the keyboard
A couple of months ago, a user of my card game reported a bug. He said that when he tried to change the names of all the robot players at once, only one of them actually changed. I tried to duplicate the problem, but couldn’t. For me, all three robot player names changed. I asked him for more details, but like many non-technical users, he wasn’t able to articulate the problem very well. He said he kept fiddling and fiddling and eventually got all three names changed. I had made a real effort to duplicate the bug and failed. So I wrote this one off as user error.
Over the next few weeks, the same bug got reported three more times. I was still not able to reproduce it. I asked for more specific instructions on how to tickle the bug, but none of this batch of people took the time to send a second email. I can’t blame them. Most people view it as a frivolous time-wasting diversion, useful for whiling away a few minutes while in line at the grocery or something.
Fortunately for me, the fifth report came from a fellow programmer. He provided very explicit instructions, and I was finally able to reproduce it. Now that I understand what was happening, it’s no small wonder that it evaded me for so long. The problem was that my mental model of how the iPhone accepts text input was wrong.
I had assumed the sequence of events was always like this:
• User touches an editable text field
• Keyboard rolls up into view
• User types some text
• User presses the return key on the keyboard
• Program forces the keyboard to disappear
• Program processes the new text
But guess what, here is another possible sequence of events:
• User touches an editable text input field
• Keyboard rolls up into view
• User types some text
• User touches a second text input field
• User edits the second input field
• User presses the return key on the keyboard
• Program forces the keyboard to disappear
• Program processes the new text
A third possible outcome is that the user navigates away from the current view to a different one, and therefore never presses the return key.
This sounds complicated, but it turns out that the fix is pretty easy. You simply have to treat “user pressed the return key” and “user finished editing” as two separate events, rather than conflating them as one.
The assumption here it that your users are typing into UITextField objects. The fix I’m proposing is for UITextField delegates, i.e., whatever object implements the UITextFieldDelegate protocol. Here’s an example implementation of one of those methods:
-(BOOL)textFieldShouldReturn:(UITextField*)textField { [textField resignFirstResponder]; return TRUE; }
This is the delegate method that gets called when the return key is pressed. I used to process the newly-edited text field contents in here. That was a mistake which led to the bug I’m talking about. In a typical implementation of this method, the only thing you want to do is make the keyboard go away, which is what the [textField resignFirstResponder] line does.
Here’s the method you should implement to deal with new text field contents:
-(void)textFieldDidEndEditing:(UITextField*)textField { // deal with new text field contents here }
I can’t show a typical implementation, because only you can decide what you should be doing with the new contents of the text field. But as far as I can tell, this method is always called when the user is finished editing the text field, regardless of why that happened. It could be because the return key was pressed, or because the user switched to a different text field, or because she switched away from the parent view altogether, or perhaps other reasons I’m not aware of.
Categories: cocoa, iphone, objective-c, programming
Comments Off
app store progress
August 6, 2009 6:40 pm
For the first month of its existence, my game was ticking along at about 200 downloads per day. I have no experience with this, but that seemed pretty good to me. Then I released 1.1.0, which is what caused that spike you see near the end there. On the first day: 192 new downloads, 949 upgrades to the new version. Second day: 576 new downloads, 1100 upgrades. It’s tapered off since then, but downloads are still significantly higher than they were before the new version came out.
According to the records I get from Apple, 6707 people downloaded version 1.0.0 of the game. So far, 3936 of those people have upgraded to the new version. Let’s hope you are all still so loyal when I get around to releasing the paid version. Heh!
Categories: iphone, itunes app store
Comments Off
NSLog() sucks
July 18, 2009 8:15 amIn a previous installment I explained why printf() sucks, and how I fixed it. Today I am going to focus on NSLog(), which sucks even worse.
Why NSLog() sucks
Here’s a typical NSLog() call:
NSLog(@"hello: %d", 6);
Note that it doesn’t require a newline character at the end of the format string. So we’ve made a little progress over printf(). It’s not until you see the output that we get to what I think is wrong with it:
2009-07-18 08:48:29.067 nslog_sucks[44201:10b] hello: 6
Sigh. Just as with printf(), NSLog() is optimized for a corner case I hardly ever need. The assumption is that I would always want to know when this action took place down to a thousandth of a second, and that I’d want to see the name of the program doing the output, the program’s PID, and so on. The output I’m interested in is drowned out by unimportant noise.
Replacing NSLog() with something better
This is how NSLog() is defined, in NSObjCRuntime.h:
FOUNDATION_EXPORT void NSLog(NSString *format, ...) __attribute__((format(__NSString__, 1, 2)));
At first glance, it appears that this would give you the same sort of warning that you get with printf() if the format string doesn’t match the arguments supplied. Sadly, I’ve never been able to coax GCC into supplying warnings in that case. But I added the same __attribute__ decoration to my NSLog() replacement, in case this is just a bug in current versions of GCC that will be fixed at some point in the future.
Finally, here’s the source code for nlog(), my NSLog() replacement that doesn’t fill the screen with unnecessary details. The header file, nslog_sucks.h:
// nslog_sucks.h -- an NSLog() alternative // by allen brunson july 18 2009 #ifndef NSLOG_SUCKS_H #define NSLOG_SUCKS_H #include <Foundation/Foundation.h> // nlog(), a better NSLog() void nlog(NSString* nfmt, ...) __attribute__((format(__NSString__, 1, 2))); #endif // NSLOG_SUCKS_H
And the implementation file, nslog_sucks.m:
// nslog_sucks.m -- an NSLog() alternative // by allen brunson july 18 2009 #include <stdio.h> #include <stdlib.h> #include <Foundation/Foundation.h> #include "nslog_sucks.h" void nlog(NSString* nfmt, ...) { va_list args = NULL; NSString* nstr = nil; va_start(args, nfmt); nstr = [[NSString alloc] initWithFormat:nfmt arguments:args]; va_end(args); puts([nstr UTF8String]); [nstr release]; nstr = nil; } int main(int argc, const char** argv) { NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; nlog(@"hello from nlog: %s %d", "text", 2); [pool release]; pool = nil; return 0; }
Categories: cocoa, iphone, mac, objective-c, programming
Comments Off
App Store rite of passage
June 25, 2009 5:21 amI just got my first iPhone app into the store! iTunes link This can be a traumatic experience for many developers, and we all learn more about what the process is like through blogs, so here’s my own war story.
I submitted my app on June 14, 2009. It went live on the store on June 24. Let’s call that ten days. Better than some, worse than others. Plenty acceptable for me.
You’ve no doubt read about the horrors of trying to get your app properly signed for deployment on hardware devices and for distribution through Apple’s store. It certainly happened to me as well. I’m not going to go into all the details, because I followed so many dead ends that I’m not sure what I did anymore. I’ll mention the main thing that bit me hard, though. My app’s name has a space in it. When Xcode expanded the ${PRODUCT_NAME} variable into my app’s identifier, it replaced that space with an underscore. This worked fine for ad hoc builds, but the app store flatly refused to accept it. The error message I got didn’t tell me what the real problem was. After much googling and reading other people’s anguished cries, I solved it by removing the underscore. It looks like current versions of Xcode expand spaces to dashes, which is hopefully acceptable for distribution.
I was most worried about HIG violations. I did a lot of things in my app that are not very, um, “Apple-like.” My game has extensive settings, but rather than creating a settings bundle, I put them into a tab on my tab bar. I spent quite a bit of time writing a view controller that uses a UIWebView to display help pages, complete with back and forward buttons for navigation. None of Apple’s apps seem to have any help text anywhere. Apple wants you to use your Default.png to make it look like your app loaded faster than it did, but to me that sounds like lying, so my version simply has the word “Loading…” on it. I created an “about” page. Perhaps worst of all, I created a view that displays the app’s memory usage statistics, which will probably confuse the hell out of most people. I very nearly took that out of the final app. But Apple didn’t give me any trouble about any of that stuff. My app sailed through the approval process with nary a peep.
Based on other people’s stories, here’s some problem areas I might have avoided. I made sure that my artist used more or less the same content for the app’s 57×57 and 512×512 icons. I read somewhere that Apple doesn’t like to see version numbers below 1.0.0, so that’s what I used for my app, although most of my projects seem to languish in the 0-dot-something range. I didn’t use any of Apple’s tab bar graphics, because they all have predefined meanings that don’t match what my tabs are for. I didn’t use any undocumented API calls, although I was sorely tempted a few times. I guess that was enough to keep me out of trouble, in this particular case.
Categories: iphone, itunes app store
Comments Off
printf() sucks
June 23, 2009 1:44 pmI am a big fan of printf()-style debugging. It helps you get an overview of a problem that traditional debuggers are not so good at. So it’s a bit unexpected that I do not like printf() itself.
Why printf() sucks
printf() is optimized for a weird corner case that you almost never need. You’ll no doubt recognize this as standard usage:
printf("Hello, World!\n")
Notice that newline character at the end. It’s a pain to type. As best as I can tell, it exists so you can do this:
… which causes the output from both printf() calls to be printed on the same line. Swell. How many times have you needed to do that? I first started programming in C in the late eighties, and my lifetime total so far is zero. I typed thousands of unnecessary newline characters before I finally wised up and wrote a replacement.
Format strings are error-prone
The most likely problem you’ll have with printf() and functions like it is a mismatch between the format string and the variables presented to it. For example:
printf("Two strings: %s %s\n", "text");
The format string calls for two strings to be printed, but you’ve only provided one. The call to printf() might work fine, crash, or print weird results, depending on what happens to be lying around on the stack. If your compiler is GCC — and it probably is, if you’re programming for any UNIX variant, including Mac OS X — there is a good workaround. Here’s my definition for echo(), my printf() replacement:
void echo(const char* tfmt, ...) __attribute__((format(printf, 1, 2)));
That weird __attribute__ business is a GCC-ism that means “this function works like printf(), and here’s the argument numbers to use for the format string and the variable args, respectively.”
This feature doesn’t work unless you specifically enable the proper GCC warning. If you’re using makefiles or the command line, pass -Wformat to the compiler. If you’re using Xcode, bring up the project information window. In the “Build” tab, there’s a section called “GCC 4.0 – Warnings.” The warning you want is labeled “Typecheck Calls to printf/scanf,” which should be enabled. Once you do that, then when you write code like this:
echo("bad format: %s %s", "text");
The compiler will give you this warning:
warning: too few arguments for format
… which saves you from the undefined behavior your program was about to be subjected to.
(C++ introduced cout, which is a printf() replacement. It handily works around the format string issue discussed here. I’ve always felt that cout introduces more problems than it solves, so I personally avoid it.)
Functions you can use
The sample code that follows includes three functions you might want to use in your own programs.
echo() — works exactly the same as printf(), except it doesn’t require a newline at the end of its format string.
sfmt() — works exactly the same as echo(), except it puts the formatted contents into a std::string object, rather than printing to stdout.
fmtArg() — useful if you want to build your own printf()-like function similar to echo() or sfmt(). Have a look at how echo() uses it, which should be enough for you to get started.
Example code
First the header file, printf_sucks.h:
// printf_sucks.h -- printf() alternative // by allen brunson june 18 2009 #ifndef PRINTF_SUCKS_H #define PRINTF_SUCKS_H // sfmt() and support functions std::string fmtArg(const char* tfmt, va_list args); std::string fmtArgLarge(int32_t byteCount, const char* tfmt, va_list args); std::string sfmt(const char* tfmt, ...) __attribute__((format(printf, 1, 2))); // echo(), a better printf() void echo(const char* tfmt, ...) __attribute__((format(printf, 1, 2))); #endif // PRINTF_SUCKS_H
Now the source file, printf_sucks.cpp:
// printf_sucks.cpp -- printf() alternative // by allen brunson june 18 2009 #include <assert.h> #include <stdio.h> #include <stdint.h> #include <stdlib.h> #include <string> #include "printf_sucks.h" void echo(const char* tfmt, ...) { va_list args = NULL; std::string text; va_start(args, tfmt); text = fmtArg(tfmt, args); va_end(args); puts(text.c_str()); } std::string fmtArg(const char* tfmt, va_list args) { static const int32_t kBufferSize = 2 * 1024; // the extra four bytes are to guard against buffer overruns char cbuf[kBufferSize + 4]; int32_t size = 0; assert(tfmt && tfmt[0]); size = vsnprintf(cbuf, kBufferSize, tfmt, args); if (size < kBufferSize) { return std::string(cbuf); } else { return fmtArgLarge(size, tfmt, args); } } // called when fmtArg() didn't have a big enough buffer std::string fmtArgLarge(int32_t byteCount, const char* tfmt, va_list args) { char* cbuf = NULL; int32_t clen = byteCount + 10; int32_t size = 0; std::string text; cbuf = static_cast<char*>(malloc(clen + 4)); if (!cbuf) return ""; size = vsnprintf(cbuf, clen, tfmt, args); assert(size < clen); text.assign(cbuf); free(cbuf); cbuf = NULL; return text; } int main(int argc, const char** argv) { echo("hello from echo: %s %d", "text", 2); return 0; } // works like echo(), but puts the formatted contents into a std::string std::string sfmt(const char* tfmt, ...) { va_list args = NULL; std::string text; assert(tfmt && tfmt[0]); va_start(args, tfmt); text = fmtArg(tfmt, args); va_end(args); return text; }
Categories: c++, linux, mac, programming
Comments Off
Drawing NSStrings in unusual rotations
June 1, 2009 8:10 pmIf you’re used to writing apps for desktop computers, the iPhone’s screen can seem awfully small. You must make creative use of every pixel available to you. One way to do that is to draw some text vertically, rather than horizontally, which I decided to do for an iPhone app I’m working on right now.
For drawing normal horizontal text, UIKit has a category called UIStringDrawing that provides convenient methods for drawing the contents of an NSString to the current graphics context, like drawAtPoint:withFont: and drawInRect:withFont:. Sadly, UIKit does not provide methods for drawing text in any other orientation except standard horizontal. I will now present an NSString category you can add to your own iPhone projects that allows you to draw vertical text from bottom to top, top to bottom, or horizontal text upside down.
My category supports the same three UITextAlignment options that UIKit provides for left, right, or centered text alignment. If using my drawInRect:... method to draw a string that’s too wide to fit within the rectangle supplied, the right end of the string will be truncated to fit, with an ellipsis character added.
UIKit does a better job of handling long strings than my code does. UIKit can wrap text to two or more lines, for example, which I did not try to emulate. The UIKit methods also allow you to provide a UILineBreakMode enum value, which gives you fine-grained control over how strings are truncated. I personally don’t need that much flexibility, so my category has no such support.
Finally, there is one last limitation of my category that might be a deal-breaker for you. The standard UIKit string-drawing functions can be used to display any Unicode character, so long as the font you’re using has a glyph for it. My category is limited to the roughly 255 characters present in the MacRoman character set. That means my code is good for displaying text in English and most European languages, like French and German, but it is completely unsuitable for text in, say, Chinese.

I’m aware that this is an outrageous limitation. You’d be hard-pressed to find a programmer who is more gung ho about Unicode than I am. I struggled mightily for several days trying to find a way around this. Sadly, this is due to the way that the underlying CoreGraphics drawing routines work, and there is no good way around it that I can see. I’ll cover this in more detail later, in case you’re interested.
Using the WBTextDrawing category
I’ve included a sample Xcode project that demonstrates the use of my WBTextDrawing category. Download the project by clicking here.
I’ve provided a screen-shot, but it isn’t much to look at. There are five strings displayed onscreen. The four strings at the edges of the view are displayed with my own WBTextDrawing category, drawn in all four available orientations. The string in the center is drawn with one of UIKit’s own drawInRect:... methods. The lines in red show the bounding rects used to draw the five strings. The four strings around the edges all have an associated green pixel, which illustrates the draw point used to draw that particular string. Naturally you wouldn’t draw the green and red bits in a real app. I added them to this demo so you’ll have a better idea of what’s going on.
Almost all the code in the project is boilerplate that can be safely ignored. To add my string-drawing category to your own program, copy WBTextDrawing.mm and WBTextDrawing.h out of this project and into your own. All other source files presented here are for demonstration purposes only.

You will note that WBTextDrawing.mm ends with an mm extension, rather than the usual m. That’s because this source file must be compiled as Objective-C++, due to the fact that it contains a small amount of C++. I know many Objective-C programmers have a strong aversion to C++, and believe me, I understand! But the underlying CoreGraphics method that’s used to draw rotated strings insists on being given a const char*. It does not work with NSString objects directly. So I chose to convert NSString objects to std::string objects just before drawing them. Yes, I could have accomplished this without leaving the confines of Objective-C, but std::string seems to me like the best tool for the job.
Specifying text drawing locations

UIKit’s UIStringDrawing category contains several drawAtPoint:... methods for drawing NSString objects. The point you pass to these methods is the far left end of the font’s baseline, as illustrated by the green point in the first figure. For the sake of compatibility, I chose to use this same convention for the drawAtPoint:... method in my own WBTextDrawing category. No matter what drawing orientation you’re using, the specified draw point is always the far left end of the font’s baseline, relative to the string being drawn. This is also the way the low-level CoreGraphics drawing routines work, conveniently enough. See the second figure for what this looks like when drawing a bottom-to-top vertical string.
UIStringDrawing also has several drawInRect:... methods. In this case, the rectangle supplied is the entire area allotted for drawing the string, illustrated by the red rectangles in figure one and figure two. Again, my own WBTextDrawing category does the exact same thing, for compatibility’s sake.
Text drawn is limited to MacRoman
This is without a doubt the worst limitation of my WBTextDrawing category. I see no good way around it, however.
Whatever method you use for drawing rotated text, you must accomplish two goals: 1) Apply a given font to the current drawing context, and 2) Draw the NSString supplied by the caller. The easiest way I can see to apply a font is to use CGContextSelectFont(). That method gives you two encoding options: kCGEncodingMacRoman, which uses the MacRoman encoding, or kCGEncodingFontSpecific, which means you must provide your own character-to-glyph translations, as far as I can tell. Ahem. UIKit can’t do this by itself, apparently, but the expectation is that us lowly app programmers should be able to do it? As if. So the only real option here is to use the MacRoman encoding, then use CGContextShowText() to display the text. This is the way my category works. Anything outside the MacRoman character set displays as gibberish.
There’s another method you can use to apply a font to the current context: CGContextSetFont(). This method doesn’t take any kind of encoding parameter at all, so it would appear to be immune from the problems presented by CGContextSelectFont(). Alas, once you’ve called that method, CGContextShowText() doesn’t work anymore. The CoreGraphics docs say you should instead call CGContextShowGlyphsAtPoint(). That function expects its caller to supply an array of glyphs, not characters. Which implies that you’ve got some method up your sleeve that will convert characters to glyphs for a given encoding. I don’t know about you, but I don’t have any such method lying around. So I’m stuck with boring old MacRoman.
This seems like an awfully strange limitation to build into the low-level CoreGraphics text drawing routines. If you know of any way around it, please tell me what it is, so I can update my text-drawing category appropriately.
Categories: cocoa, iphone, objective-c, programming
Comments Off

