Sunday, May 31, 2009

Tutorial Mode For An iPhone App

One of my testers suggested that I have a "tutorial" for new users of my app: Help screens that pop up the first time a user visits a feature. There are many ways to do this, of course, but here's the implementation I designed. A client invokes the tutorial system in a simple way:

[TutorialSystem showTutorialForKey:@"Welcome"];

The TutorialSystem class takes over the rest through a series of class methods. (There's never an instance of a TutorialSystem object. Not a very object-oriented approach, but the tutorial system doesn't need to maintain state between invocations.)

Obviously I only want to show a tutorial screen once, so showTutorialForKey: first checks to see if the user has already seen the screen. I have an "application state" dictionary file that gets used for miscellaneous persistence: the last tab the user looked at before shutting down, the date they were looking at in the "by day" view, and so forth. The tutorial system uses the same dictionary to keep track of which tutorial screens the user has seen. In particular, it looks for a key named "hasSeen" followed by the name of the key passed in to the showTutorialForKey method. For the example above, the dictionary would have a key named "hasSeenWelcome."

+ (BOOL) userHasSeenKey: (NSString *)key {
return NO;
NSMutableDictionary *prefs = [[TutorialSystem appDelegate] appState];
NSNumber *didSee = [prefs objectForKey:[TutorialSystem prefsSeenKeyForKey:key]];

return didSee != nil && [didSee intValue] != 0;

(I have a #define that lets me always have tutorial screens show up. This is helpful for testing.)

If the system decides that it needs to show the tutorial screen, it uses the passed-in key as the key for a line in a strings file, the resource files Cocoa uses for localization of text. It also uses the key plus the text " Title" as the key in the strings file for the title of the tutorial screen. That means there's a line in my strings file that maps the tutorial body text to the key "Welcome" and another line that maps the tutorial screen title to "Welcome Title."

From there, it loads an xib file that I built in Interface Builder and lays it over the existing view. That file contains a screen-sized view that doesn't allow user interaction, but has an opaqueness level of .4. That way the underlying screen will still be visible. The xib file also contains a "tutorial screen" view that contains three subviews: a UILabel for the title, a UITextView for the tutorial body, and a UILabel that says "Tap to continue." The tutorial screen view handles all the user interactions, and interprets any touch to mean "go away." Here's what it looks like.

Finally, of course, I register that the user has seen the tutorial screen by placing the appropriate entry into the application state dictionary.

Here's the full showTutorialForKey: method:

+ (void) showTutorialForKey: (NSString *)key {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
if ([TutorialSystem userHasSeenKey:key]) {
[pool release];

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

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

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

[TutorialSystem registerUserSeesKey:key];

[pool release];


1 comment:

  1. I wrote another Tutorial system for the iphone which simply points out buttons with arrows and text. You can download it from github -