Geeks With Blogs
Create Fun Things - Samer's Developer Blog Development Blog for C#, .NET and Obj-C

This is just a quick tutorial to help you write in an in-app help menu using a .plist file. As apps become more complicated (especially if you continue to add onto your 1.0 version with more features), it's easy to get lost in them. And a good built-in support system is appreciated by everyone. I also prefer to keep it in-app because then it's formatted properly and your users won't have to open up a browser and check, which isn't always available (or isn't always formatted properly).

Ok so I decided to use a .plist file because it'd be the easiest way to structure the document and make changes. If you wanted to get really fancy, you could host the .plist file online (and have it pull from there) so that you can make edits to it without having to re-submit the app. But that's out of the scope of this particular post.

Begin by adding the .plist to your project. You can do this easily by right clicking on your project and clicking "Add.." and then "Add New File..." :

AddNewPlist.jpg

For my plist, I decided to make the root object an array and have each object in that array be a dictionary. The reason for choosing an Array (instead of the default Dictionary) is that you can order the menu items in the file itself, without having to do any extra work once you parse it. Dictionaries result in un-ordered lists, which would be silly, since you typically would want your help menu to be organized in a logical manner (going from the most basic to advanced features, for example.) So in each dictionary, I make the first item keyed to "Title" with the value of the title that the section will have. In this manner, I can create a help menu that uses the UITableViewGroup display, and have several sections with rows in each. (Once you see some pictures, I think it'll be pretty clear.)

Here is what my final demo plist looks like. I've used my app (Compositions) as an example: PlistFile.jpg

And just as a preview of what this will end up looking like, here is the final tableview in my app:

FinalHelpTableView.jpg

Ok, so to get from the file to that screenshot is actually pretty easy. I added a new UITableViewController to my project and named it HelpViewController. I checked the box to subclass it from the UITableViewController class so that it would add in the methods that I need.

I am using a custom class that I wrote called "MenuItem". MenuItem is a generic class I use for any time I need to write a custom UITableView item list. It has the ability to chain indefinitely, so that I can easily create sections and rows with it, and also include a subtitle, description, item name, a key (which is useful when your positioning is independent of the order you add the items), etc. Here is the class definition:

As I mentioned, the subMenuItems is useful to add another array of MenuItems, so that I can easily create a hierarchy in a menu.

@interface MenuItem : NSObject {

 NSUInteger itemKey;

 NSString *itemDescription;

 NSString *itemSubtitle;

 NSMutableArray *subMenuItems;

}

@property (nonatomic, assign) NSUInteger itemKey;

@property (nonatomic, retain) NSString *itemDescription;

@property (nonatomic, retain) NSString *itemSubtitle;

@property (nonatomic, retain) NSMutableArray *subMenuItems;


Back to my HelpViewController! I create a NSMutableArray to hold my menu items:

 NSMutableArray *menuItems;

And I initialize it in my -(id)init method:

-(id)init {
 [super initWithStyle:UITableViewStyleGrouped];
 menuItems = [[NSMutableArray alloc] init];
 return self;
}


I'm choosing to create the actual menu in my ViewDidLoad method, so that if I run low on memory while I'm off the screen, I can dump it and re-create it if they come back to it.

 

Here is the code to build the menu, using my custom class and the plist file. I'll explain everything after:

-(void)loadHelpChapters {
 NSBundle *bundle = [NSBundle mainBundle];
 NSString *plistPath = [bundle pathForResource:@"HelpList" ofType:@"plist"];
 NSArray *rootArray = [[NSArray alloc] initWithContentsOfFile:plistPath];
 MenuItem *mi = nil;
 int k = 0;

 for (NSDictionary* chapter in rootArray ) {
 NSString *chapTitle = [chapter valueForKey:@"Title"];
 mi = [[MenuItem alloc] initWithKey:k andDescription:chapTitle];

 [chapter enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
 if (![key isEqualToString:@"Title"]) {

 MenuItem *subMi = [[MenuItem alloc] initWithKey:k andDescription:key andSubtitle: obj];
 [[mi subMenuItems] addObject:subMi];
 [subMi release];

 }

 }];

 [menuItems addObject:mi];

 [mi release]; mi = nil;

 k++;

 } 

 [rootArray release];

}


So what's happening here?  First I'm getting a reference to my bundle (which is your packaged app, essentially) and then getting the address of the actual .plist. I've named my HelpList so that's what's happening in line 2. I then initialize an array (which is the root object of the file) using NSArray's initWithContentsOfFile: method. That will open up the file and load all the root objects into an array.

 

Each object is now a NSDictionary, which I'm calling "chapter". I'm going to enumerate through each one and do a couple things.

First I'm going to create my section MenuItem. This is going to represent the tableView section and will have several rows in it. I ask for dictionary for the "Title" value and initialize a MenuItem with that. I am not using the key for anything in this instance (the key is just an int value) but I am adding an int k to it, just in case I ever want to use its position for anything.

Next comes the really cool part--using blocks! I need to go through the rest of the dictionary, getting everything but the Title value, to add each row to this section. I use the method -enumerateKeysAndObjectsUsingBlock which essentially goes through each dictionary item and exposes both its key and value. The key in this case is the section title, and the value in my example is the text that will appear when the user taps on the item.

For each of these enumerated dictionary values, I create a child MenuItem (called subMi) and add it to my current section MenuItem. Once added, I release it.

Once I'm done enumerating, I add this section MenuItem to my ViewController's array and release that as well, to be a good memory citizen.

Once it's done going through all the root objects, I release the rootArray object I created, freeing up that memory as well.

Now for the tableView methods. As I've mentioned, we now have a top level array of MenuItems that represents all the sections. And inside each of those, we have children MenuItems that represent rows. So this is basically an exact mirror to how tableViews work, with NSIndexPath. So it's pretty simple:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
 // Return the number of sections, which is just how many items we have in our menuItems array.
return [menuItems count];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
 // Return the number of rows in the section.
 // First get the top level item using section
MenuItem *mi = [menuItems objectAtIndex:section];
// Then return the number of children items from the top level one.
 return [[mi subMenuItems] count];
}

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

static NSString *CellIdentifier = @"Cell";
 UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];

if (cell == nil) {
 cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
 // Get the top level first from the section
 MenuItem *mi = [menuItems objectAtIndex:[indexPath section]];
 // Then get the child level item from the row

 MenuItem *subMi = [[mi subMenuItems] objectAtIndex:[indexPath row]];
 [[cell textLabel] setText:[subMi itemDescription]];

 return cell;
}

-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {

 // Pull from the top level to get the section title
 MenuItem *mi = [menuItems objectAtIndex:section];

 return [mi itemDescription];
}


I hope this proves helpful for someone! It might seem like a lot of work, but it's really not too bad. And as I said, now you have a code-independent method of adding and removing help items. You can do all your text editing in your .plist editor, and even put it online to update it there. You could design a system that checks for an updated help file on load (if network is present) and pull it down real quick. Plists are small text files essentially, so it would be pretty quick.

 

Posted on Tuesday, April 5, 2011 9:52 AM | Back to top


Comments on this post: Creating an In-App Help Menu with a .plist file

# re: Creating an In-App Help Menu with a .plist file
Requesting Gravatar...
Thanks Samer, I have been looking for how to set up a sectioned tableview from a plist, so this certainly is useful!

The urls to your images appear to be broken though, would be useful if you could fix these.

Thanks
Left by James Brocklehurst on Aug 14, 2011 10:51 AM

Your comment:
 (will show your gravatar)


Copyright © samerpaul | Powered by: GeeksWithBlogs.net