In my Fightcard application, I had to implement a lot of “picker” screens – screens that essentially let the user select one of a list of options, much as a drop-down does in a conventional GUI. Since the behavior of all these screens was so similar, I opted to create a common ChoiceMenu
class. I now present it for your consideration.
Examples
Here are some of the “picker” screens in my application:
They all follow the same rules:
- They’re accessed from a parent screen, which shows the current value of a variable
- They present all possible choices for that variable, with a checkmark next to the current value
- When the user selects a new value, the screen is popped off the stack, and the parent controller updates its model and view
- The user may cancel the selection process by touching the back button
- Each screen has its own title, and possibly displays a prompt as a section header
Code
Without further ado, here are the header and implementation files for my ChoiceMenu
class:
//
// ChoiceMenu.h
// Fightcard
//
// Created by Michael Heyeck on 8/31/09.
//
#import <UIKit/UIKit.h>
@class ChoiceMenu;
@protocol ChoiceMenuDelegate
- (void)choiceMenu:(ChoiceMenu*)choiceMenu didEndWithObject:(id)object;
@end
@interface ChoiceMenu : UITableViewController {
NSString* prompt;
NSArray* choices;
NSUInteger selection;
id<ChoiceMenuDelegate> delegate;
}
@property (nonatomic, assign) NSUInteger selection;
@property (nonatomic, assign) id<ChoiceMenuDelegate> delegate;
- (id)initWithChoices:(NSArray*)a_choices;
- (id)initWithPrompt:(NSString*)a_prompt forChoices:(NSArray*)a_choices;
@end
//
// ChoiceMenu.m
// Fightcard
//
// Created by Michael Heyeck on 8/31/09.
//
#import "ChoiceMenu.h"
@implementation ChoiceMenu
- (NSUInteger)selection
{
return selection;
}
- (void)setSelection:(NSUInteger)a_selection
{
selection = a_selection;
[[self tableView] reloadData];
[[self tableView] scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:selection inSection:0] atScrollPosition:UITableViewScrollPositionMiddle animated:NO];
}
@synthesize delegate;
// New D. I.(s)
- (id)initWithChoices:(NSArray*)a_choices
{
return (self = [self initWithPrompt:nil forChoices:a_choices]);
}
- (id)initWithPrompt:(NSString*)a_prompt forChoices:(NSArray*)a_choices
{
if (self = [super initWithStyle:UITableViewStyleGrouped])
{
prompt = [a_prompt retain];
choices = [a_choices retain];
selection = -1;
}
return self;
}
// Super's D. I.
- (id)initWithStyle:(UITableViewStyle)style
{
return (self = [self initWithChoices:nil]);
}
// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad
{
[super viewDidLoad];
// Needed to make scrollToRowAtIndexPath:atScrollPosition:animated: work properly near table edges
static const NSUInteger navBarHeight = 44;
CGRect appFrame = [[UIScreen mainScreen] applicationFrame];
self.tableView.frame = CGRectMake(0, navBarHeight, appFrame.size.width, appFrame.size.height-navBarHeight);
}
/*
// Override to allow orientations other than the default portrait orientation.
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
*/
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview
// Release anything that's not essential, such as cached data
}
- (void)dealloc
{
[prompt release];
[choices release];
[super dealloc];
}
#pragma mark UITableViewDataSource methods
- (NSString*)tableView:(UITableView*)tv titleForHeaderInSection:(NSInteger)section
{
return prompt;
}
- (NSInteger)tableView:(UITableView*)tv numberOfRowsInSection:(NSInteger)section
{
return [choices count];
}
- (UITableViewCell*)tableView:(UITableView*)tv cellForRowAtIndexPath:(NSIndexPath*)indexPath
{
static NSString* cellId = @"CellId";
UITableViewCell* cell = [tv dequeueReusableCellWithIdentifier:cellId];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithFrame:CGRectMake(0, 0, 300, 44) reuseIdentifier:cellId] autorelease];
cell.font = [UIFont systemFontOfSize:18];
}
cell.text = [NSString stringWithFormat:@"%@",[choices objectAtIndex:indexPath.row]];
cell.accessoryType = (selection==indexPath.row)?UITableViewCellAccessoryCheckmark:UITableViewCellAccessoryNone;
return cell;
}
#pragma mark UITableViewDelegate methods
- (void)tableView:(UITableView*)tv didSelectRowAtIndexPath:(NSIndexPath*)indexPath
{
[tv deselectRowAtIndexPath:indexPath animated:NO];
if (selection != indexPath.row)
{
// Uncheck old row
[tv cellForRowAtIndexPath:[NSIndexPath indexPathForRow:selection inSection:0]].accessoryType = UITableViewCellAccessoryNone;
// Check new row
[tv cellForRowAtIndexPath:indexPath].accessoryType = UITableViewCellAccessoryCheckmark;
// Update model
selection = indexPath.row;
// Invoke delegate method
[delegate choiceMenu:self didEndWithObject:[choices objectAtIndex:selection]];
}
}
@end