Wednesday, February 25, 2009

Editable Select List

For a project I was working on, we had a text field that the users would tend to enter the same handful of values over and over. In fact, the fact that they had to keep entering the same values over and over was quite frustrating to our testers. But we couldn't provide a set list, because it wouldn't be the same values for all users. They needed the flexibility to add any value they needed, but wanted the convenience to not have to enter ones that they had already entered. On a desktop app, the likely answer would have been a combo-box, or a text field with type-ahead that would allow the user to type only a few characters of the value and then hit tab or return to select it.

The iPhone doesn't have combo-boxes, and type-ahead would be a pain to implement and I had concerns that it might get dinged in the review process (Yes, Apple, your review policies are definitely having a chilling effect). The answer I came up with for handling this situation was to create an editable selection list controller. It works just like the Generic Selection List Controller I posted about a week ago, except that it tacks one item onto the end of the table to allow the user to add a new item to the list:


When you select that last item, it uses the Multiple Text Field Editing Controller to prompt the user for the new value:



At some point, I'd like to refactor this class, and the SelectionListViewcontroller into one class, as there is a lot of common ground between them, but for now, it's a separate class. You must have the TextFieldEditingViewcontroller class in your project also, because it uses that to let the user enter new values.

EditableSelectionListViewController.h
//
// SelectionListViewController.h
//
// Created by Jeff LaMarche on 2/18/09.
// Copyright 2009 Jeff LaMarche Consulting. All rights reserved.
//

#import <UIKit/UIKit.h>
#import "AbstractGenericViewController.h"
#import "TextFieldEditingViewController.h"

@protocol EditableSelectionListViewControllerDelegate <NSObject>
@required
- (void)rowChosen:(NSInteger)row fromArray:(NSMutableArray *)theList;
@end


@interface EditableSelectionListViewController : AbstractGenericViewController <TextFieldEditingViewControllerDelegate>
{
NSMutableArray *list;
NSIndexPath *lastIndexPath;
NSInteger initialSelection;

id <EditableSelectionListViewControllerDelegate> delegate;
}

@property (nonatomic, retain) NSIndexPath *lastIndexPath;
@property (nonatomic, retain) NSArray *list;
@property NSInteger initialSelection;
@property (nonatomic, assign) id <EditableSelectionListViewControllerDelegate> delegate;
@end



EditableSelectionListViewController.m
//
// SelectionListViewController.m
//
// Created by Jeff LaMarche on 2/18/09.
// Copyright 2009 Jeff LaMarche Consulting. All rights reserved.
//

#import "EditableSelectionListViewController.h"


@implementation EditableSelectionListViewController
@synthesize list;
@synthesize lastIndexPath;
@synthesize initialSelection;
@synthesize delegate;
-(IBAction)save
{
[self.delegate rowChosen:[lastIndexPath row] fromArray:list];
[self.navigationController popViewControllerAnimated:YES];
}

#pragma mark -
- (id)initWithStyle:(UITableViewStyle)style
{
initialSelection = -1;
return self;
}

- (void)viewWillAppear:(BOOL)animated
{
// Check to see if user has indicated a row to be selected, and set it
if (initialSelection > - 1 && initialSelection < [list count])
{
NSUInteger newIndex[] = {0, initialSelection};
NSIndexPath *newPath = [[NSIndexPath alloc] initWithIndexes:newIndex length:2];
self.lastIndexPath = newPath;
[newPath release];
}


[super viewWillAppear:animated];
}

- (void)dealloc
{
[list release];
[lastIndexPath release];
[super dealloc];
}

#pragma mark -
#pragma mark Tableview methods
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [list count] + 1;
}

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

static NSString *SelectionListCellIdentifier = @"SelectionListCellIdentifier";

UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:SelectionListCellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:SelectionListCellIdentifier] autorelease];
}


NSUInteger row = [indexPath row];
NSUInteger oldRow = [lastIndexPath row];
if (row >= [list count])
{
cell.font = [UIFont boldSystemFontOfSize:19.0];
cell.text = NSLocalizedString(@"Other…", @"Other…");
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
}

else
{
cell.font = [UIFont systemFontOfSize:19.0];
cell.text = [list objectAtIndex:row];
cell.accessoryType = (row == oldRow && lastIndexPath != nil) ? UITableViewCellAccessoryCheckmark : UITableViewCellAccessoryNone;

}


return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
int newRow = [indexPath row];
int oldRow = [lastIndexPath row];

if (newRow < [list count])
{
if (newRow != oldRow)
{
UITableViewCell *newCell = [tableView cellForRowAtIndexPath:indexPath];
newCell.accessoryType = UITableViewCellAccessoryCheckmark;

UITableViewCell *oldCell = [tableView cellForRowAtIndexPath: lastIndexPath];
oldCell.accessoryType = UITableViewCellAccessoryNone;

lastIndexPath = indexPath;
}

}

else
{
TextFieldEditingViewController *controller = [[TextFieldEditingViewController alloc] initWithStyle:UITableViewStyleGrouped];
controller.fieldKeys = [NSArray arrayWithObject:@"newValue"];
controller.fieldNames = [NSArray arrayWithObject:NSLocalizedString(@"New Item", @"New Item")];
controller.fieldValues = [NSArray arrayWithObject:@""];
controller.delegate = self;
[self.navigationController pushViewController:controller animated:YES];
}

[tableView deselectRowAtIndexPath:indexPath animated:YES];
}

#pragma mark -
- (void)selectRow:(NSIndexPath *)theIndexPath
{
//[self.tableView selectRowAtIndexPath:theIndexPath animated:YES scrollPosition:UITableViewScrollPositionBottom];
[self tableView:self.tableView didSelectRowAtIndexPath:theIndexPath];
}

- (void)valuesDidChange:(NSDictionary *)newValues
{
NSString *newVal = [newValues objectForKey:@"newValue"];
[list addObject:newVal];
//[self.tableView reloadData];

[list sortUsingSelector:@selector(compare:)];
NSUInteger theIndices[] = {0, [list indexOfObject:newVal]};
NSIndexPath *theIndexPath = [[NSIndexPath alloc] initWithIndexes:theIndices length:2];
[self performSelector:@selector(selectRow:) withObject:theIndexPath afterDelay:0.05];
// [self tableView:self.tableView didSelectRowAtIndexPath:theIndexPath];
[self.tableView reloadData];
}

@end


No comments:

Post a Comment