More reusable classes, but this time, not from me. Joe Hewitt of Facebook recently created an open source project called 320 that contains a bunch of re-usable objects. They look very polished and nice and definitely worth taking a look at.
via Daring Fireball.
Showing posts with label Code Reuse. Show all posts
Showing posts with label Code Reuse. Show all posts
Monday, March 23, 2009
Friday, February 27, 2009
Reusable Code in Google Code
As per a reader request, I have created a repository with all of the re-usable code that I've posted here in my blog. You can find the repository over at Google Code.
I'll try and keep this updated with bug fixes and also add any new reusable code to the repository.
I'll try and keep this updated with bug fixes and also add any new reusable code to the repository.
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
EditableSelectionListViewController.m
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
Friday, January 23, 2009
A Better Generic Date Picker
I've tweaked my generic date picker class so it matches, pixel-for-pixel, the date picker used by Apple's built-in apps like the Address Book (Contacts.app), and also fixed some bugs in the previous version.

Here is the code that implements it. Use is exactly the same as with the previous version.
Note: if you copied this before about 5:30pm EST, Friday January 23, 2009, you might want to re-copy it. There was a memory leak thanks to me forgetting to release the the date formatter instance.
DateViewController.h
DateViewController.m

Here is the code that implements it. Use is exactly the same as with the previous version.
Note: if you copied this before about 5:30pm EST, Friday January 23, 2009, you might want to re-copy it. There was a memory leak thanks to me forgetting to release the the date formatter instance.
DateViewController.h
/*
DateViewController.h
*/
#import <UIKit/UIKit.h>
@protocol DateViewDelegate <NSObject>
@required
- (void)takeNewDate:(NSDate *)newDate;
- (UINavigationController *)navController; // Return the navigation controller
@end
@interface DateViewController : UIViewController <UITableViewDelegate, UITableViewDataSource>
{
UIDatePicker *datePicker;
UITableView *dateTableView;
NSDate *date;
id <DateViewDelegate> delegate; // weak ref
}
@property (nonatomic, retain) UIDatePicker *datePicker;
@property (nonatomic, retain) UITableView *dateTableView;
@property (nonatomic, retain) NSDate *date;
@property (nonatomic, assign) id <DateViewDelegate> delegate;
-(IBAction)dateChanged;
@endDateViewController.m
/*
DateViewController.m
*/
#import "DateViewController.h"
@implementation DateViewController
@synthesize datePicker;
@synthesize dateTableView;
@synthesize date;
@synthesize delegate;
-(IBAction)dateChanged
{
self.date = [datePicker date];
[dateTableView reloadData];
}
-(IBAction)cancel
{
[[self.delegate navController] popViewControllerAnimated:YES];
}
-(IBAction)save
{
[self.delegate takeNewDate:date];
[[self.delegate navController] popViewControllerAnimated:YES];
}
- (void)loadView
{
UIView *theView = [[UIView alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.view = theView;
[theView release];
UITableView *theTableView = [[UITableView alloc] initWithFrame:CGRectMake(0.0, 67.0, 320.0, 480.0) style:UITableViewStyleGrouped];
theTableView.delegate = self;
theTableView.dataSource = self;
[self.view addSubview:theTableView];
self.dateTableView = theTableView;
[theTableView release];
UIDatePicker *theDatePicker = [[UIDatePicker alloc] initWithFrame:CGRectMake(0.0, 200.0, 320.0, 216.0)];
theDatePicker.datePickerMode = UIDatePickerModeDate;
self.datePicker = theDatePicker;
[theDatePicker release];
[datePicker addTarget:self action:@selector(dateChanged) forControlEvents:UIControlEventValueChanged];
[self.view addSubview:datePicker];
UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc]
initWithTitle:NSLocalizedString(@"Cancel", @"Cancel - for button to cancel changes")
style:UIBarButtonItemStylePlain
target:self
action:@selector(cancel)];
self.navigationItem.leftBarButtonItem = cancelButton;
[cancelButton release];
UIBarButtonItem *saveButton = [[UIBarButtonItem alloc]
initWithTitle:NSLocalizedString(@"Save", @"Save - for button to save changes")
style:UIBarButtonItemStylePlain
target:self
action:@selector(save)];
self.navigationItem.rightBarButtonItem = saveButton;
[saveButton release];
self.view.backgroundColor = [UIColor groupTableViewBackgroundColor];
}
- (void)viewWillAppear:(BOOL)animated
{
if (self.date != nil)
[self.datePicker setDate:date animated:YES];
else
[self.datePicker setDate:[NSDate date] animated:YES];
[super viewWillAppear:animated];
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
- (void)dealloc
{
[datePicker release];
[dateTableView release];
[date release];
[super dealloc];
}
#pragma mark -
#pragma mark Table View Methods
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *DateCellIdentifier = @"DateCellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:DateCellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:DateCellIdentifier] autorelease];
cell.font = [UIFont systemFontOfSize:17.0];
cell.textColor = [UIColor colorWithRed:0.243 green:0.306 blue:0.435 alpha:1.0];
}
NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
[formatter setDateFormat:@"MMMM dd, yyyy"];
cell.text = [formatter stringFromDate:date];
[formatter release];
return cell;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 1;
}
@end
Subscribe to:
Posts (Atom)