Friday, September 4, 2009

Tutorial System, Part 2: Variadic Methods

In my last post, I mentioned a simple tutorial system that I wrote for my iPhone app: When I pass a key to the system, new users get a text box explaining the purpose of each screen, tap to close it, and then never see it again.

It worked well enough, though my testers have some usability comments that I need to address. But as I started working on version 1.1 of my app*, I realized that I had new needs. I had written, as I try to remember to do despite the temptation to overdesign, the simplest thing that could possibly work. That no longer fit the bill. (Writing the simplest thing that can possibly work assumes that design requirements will change. But since you can't predict those changes, even when you're the one creating them, you shouldn't waste time designing for requirements that don't yet exist. Address the problem in front of you, get functional code up and testable, and then refactor to simplify.)

I wanted the system to handle upgraders, so that upgraders would see text just about the new features, while new users would see a screen giving an overview of the page, including the new features. The key was that when a new user dismissed the tutorial box, s/he would never see the one for upgraders (because there would be no need).

I toyed with a few ideas, but I finally settled on one: Rather than passing a single key to the system, I'd pass a list of keys. The system would iterate through the keys until it found one the user hadn't seen, show the tutorial text for that key, register that the user had seen the key, and then register every subsequent key in the list.

In order to make this work, I used a variadic method, one with an indeterminate number arguments. It's been a while since I've been deep in a C-based language, so I had to refresh my memory about implementing them. (Curiously, the Objective-C bible, Programming in Objective-C, doesn't tell you how to implement them. Perhaps the new edition does.) They rely on the stdargs.h macros, va_start, va_arg, and va_end. In Objective-C, the convention is that the last item in the list be nil. For example, see NSArray's initWithObjects: method.

If you want to implement your own variadic method in Objective-C, here's the basic pattern (you don't need to import stdarg.h if you're importing the Foundation classes):


- (void) method: (id) arg, ... {
va_list argList; // special datatype for the argument list
va_start(argList,arg); // sets up the iteration

id curArg = arg;
while (curArg != nil) {
// do something
curArg = va_arg(argList,id); // pass the datatype you expect to find
}
va_end(argList);
}



Here's the code in the context of my Tutorial system:


+ (void) showTutorialForKey: (NSString *)key, ... {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
va_list argPtr;
va_start(argPtr,key);

NSString *curArg = key;

while (curArg != nil) {
if ([TutorialSystem userHasSeenKey:curArg]) {
curArg = va_arg(argPtr,id);
continue;
}

NSString *titleKey = [NSString stringWithFormat:@"%@ Title",curArg];

NSString* title =
[[NSBundle mainBundle] localizedStringForKey:titleKey value:@"" table:@"Tutorial"];
NSString* text =
[[NSBundle mainBundle] localizedStringForKey:curArg value:@"" table:@"Tutorial"];

TutorialController *controller =
[[TutorialController alloc] initWithTitle:title tutorialText:text];
[[[self appDelegate] window] addSubview:[controller view]];

// clear the user for all the rest of the args
while (curArg != nil) {
[TutorialSystem registerUserSeesKey:curArg];
curArg = va_arg(argPtr,id);
}
}

va_end(argPtr);
[pool release];
}



* I should note that 1.0 of my app is not yet out, despite being published on the App Store, because of some bureaucracy with my employer. Assuming it's going to get resolved. I've started on 1.1.

No comments:

Post a Comment