Ipphones

  • Subscribe to our RSS feed.
  • Twitter
  • StumbleUpon
  • Reddit
  • Facebook
  • Digg

Sunday, 28 December 2008

Reusable Controllers

Posted on 11:49 by Unknown
I thought I'd take a break from the OpenGL stuff to finish a post I've been thinking about doing for a while. One misconception that a lot of newer Cocoa/Cocoa Touch developers seem to have is that your application controller classes have to be completely custom. There is some basis for this belief, as every application will need to have some functionality that is specific to itself, but there are a number of generic, reusable controller classes, such as UITableViewController and UINavigationController that handle some or all aspects involved with controlling a certain type of user interface.

Yet, a lot of people don't carry this idea of reusability into their own controller code for some reason. All too often you see projects with several controller classes that are all very similar to each other. One place I've seen this a lot is when people implement table-based detail editing views like the ones used in the Contacts application. Here is an example of what I'm talking about:

I'm honestly surprised that Apple doesn't provide a generic controller class for this particular scenario, but they don't. There's no reason why we can't create our own generic editable detail pane controller class.

I'm going to provide the code for a version that I've used in a few projects. It allows the editing of up to five text fields. The reason only five fields is supported is because that's all that can be displayed along with the keyboard. If you need more than five fields, you should probably split it into multiple drill downs.

To use this class, you create the controller and set yourself as the delegate. You then set three properties. All three arrays represent the fields to be displayed, so the item at index 0 in all three arrays is different data about the same field, like so:
TextFieldEditingViewController *controller = [[TextFieldEditingViewController alloc] initWithStyle:UITableViewStyleGrouped];
controller.delegate = self;

controller.fieldNames = myArrayContainingTheLabelsToBeDisplayed;
controller.fieldKeys = myArrayContainingTheKeyValuesForTheItemToBeDisplayed;
controller.fieldValues = myArrayContainingTheCurrentValuesForTheItemsToBeDisplayed;

fieldNames should contain the label to be used by the detail controller for each item. This is the text in blue in the screenshot above that the user sees. The fieldKeys is the key that corresponds to each field. This will generally be the property name for the field being edited, and will be used in the method that is used to pass values back to the delegate. fieldValues will hold the current values for each of the fields. All threes of these properties must be set, and they must be NSArrays or a subclass of it. All three must contain the same number of objects, and all three must contain five objects or less.

There's another optional property that you can set: keyboardTypes, which is a five element array of UIKeyboardTypes. This can be used to tell the detail controller what type of keyboard to show the user for each field that can be edited. These all default to UIKeyboardTypeAlphabet, but if, say, one of the values you want to edit is phone number, you can pass UIKeyboardTypePhonePad for the array index that corresponds to that field.

Notice in the pseudo-code above that we set self as the controller's delegate. This is how we will be notified if the user edited any of the fields. The valuesDidChange: method will be called on the delegate, passing a dictionary using key-value coding that informs the delegate of all the changes made if the user pressed the Save button. The delegate is then responsible for taking the values from that array and stuffing them in the appropriate place.

This class is not as polished and hasn't been as thoroughly tested as Apple's generic controller classes, but it sure has reduced the number of controller classes I need in some of my table-based applications. So, without further ado, here she is:

TextFieldEditingViewController.h
/*
TextFieldEditingViewController.h
*/


#import <UIKit/UIKit.h>
#define kDefaultLabelTag 50002

@protocol TextFieldEditingViewControllerDelegate <NSObject>
@required
- (void)valuesDidChange:(NSDictionary *)newValues;
- (UINavigationController *)navController; // Return the navigation controller
@end


@interface TextFieldEditingViewController : UITableViewController <UITextFieldDelegate> {

NSArray *fieldNames; // Field name to be displayed to user
NSArray *fieldKeys; // Key value to be used in dictionary when values are passed back to delegate
NSArray *fieldValues; // Starting display values for each field. Values should be strings.

NSMutableArray *changedValues; // Changes will be stored in this array , which will also be passed back to delegate on save
UIKeyboardType keyboardTypes[5]; // Keyboard types for each field

id <TextFieldEditingViewControllerDelegate> delegate; // Delegate who will received the changed values. Delegate
// is responsble for converting back from string if necessary

UITextField *textFieldBeingEdited; // The field currently being edited

UIBarButtonItem *oldLeftButton; // Used to restore old button values
UIBarButtonItem *oldRightButton;
}

@property (nonatomic, retain) NSArray *fieldNames;
@property (nonatomic, retain) NSArray *fieldKeys;
@property (nonatomic, retain) NSArray *fieldValues;
@property (nonatomic, retain) NSMutableArray *changedValues;
@property (nonatomic, assign /* for weak ref */) id <TextFieldEditingViewControllerDelegate> delegate;
@property (nonatomic, retain) UITextField *textFieldBeingEdited;
@property (nonatomic, retain) UIBarButtonItem *oldLeftButton;
@property (nonatomic, retain) UIBarButtonItem *oldRightButton;
-(IBAction)cancel;
-(IBAction)save;
-(IBAction)textFieldDone:(id)sender;
-(void)setKeyboardType:(UIKeyboardType)theType forIndex:(NSUInteger)index;
@end




TextFieldEditingViewController.m
/*
TextFieldEditingViewController.m

*/

#import "TextFieldEditingViewController.h"
#import "BirthdaysAppDelegate.h"

@implementation TextFieldEditingViewController
@synthesize fieldNames;
@synthesize fieldKeys;
@synthesize fieldValues;
@synthesize delegate;
@synthesize changedValues;
@synthesize textFieldBeingEdited;
@synthesize oldLeftButton;
@synthesize oldRightButton;
- (id)initWithStyle:(UITableViewStyle)style
{
if (self = [super initWithStyle:style])
{
for (int i =0; i < 5; i++)
keyboardTypes[i] = UIKeyboardTypeAlphabet;
}
return self;
}
-(IBAction)textFieldDone:(id)sender
{
UITableViewCell *cell = (UITableViewCell *)[[(UIView *)sender superview] superview];
UITableView *table = (UITableView *)[cell superview];
NSIndexPath *textFieldIndexPath = [table indexPathForCell:cell];
NSUInteger row = [textFieldIndexPath row];
row++;
if (row >= [fieldNames count])
row = 0;
NSUInteger newIndex[] = {0, row};
NSIndexPath *newPath = [[NSIndexPath alloc] initWithIndexes:newIndex length:2];
UITableViewCell *nextCell = [self.tableView cellForRowAtIndexPath:newPath];
UITextField *nextField = nil;
for (UIView *oneView in nextCell.contentView.subviews)
{
if ([oneView isMemberOfClass:[UITextField class]])
nextField = (UITextField *)oneView;
}
[nextField becomeFirstResponder];

}
- (void)viewWillAppear:(BOOL)animated
{
self.oldLeftButton = self.navigationItem.leftBarButtonItem;
self.oldRightButton = self.navigationItem.rightBarButtonItem;

UIBarButtonItem *saveButton = [[UIBarButtonItem alloc]
initWithTitle:@"Save"
style:UIBarButtonItemStylePlain
target:self
action:@selector(save)]
;
self.navigationItem.rightBarButtonItem = saveButton;
[saveButton release];

UIBarButtonItem *cancelButton = [[UIBarButtonItem alloc]
initWithTitle:@"Cancel"
style:UIBarButtonItemStylePlain
target:self
action:@selector(cancel)]
;
self.navigationItem.leftBarButtonItem = cancelButton;
[cancelButton release];

[super viewWillAppear:animated];
}
- (void)viewWillDisappear:(BOOL)animated {
self.navigationItem.leftBarButtonItem = oldLeftButton;
self.navigationItem.rightBarButtonItem = oldRightButton;
[super viewWillDisappear:animated];
}
-(IBAction)cancel
{
[[self.delegate navController] popViewControllerAnimated:YES];
}
-(IBAction)save
{
if (textFieldBeingEdited != nil)
[changedValues replaceObjectAtIndex:textFieldBeingEdited.tag withObject:textFieldBeingEdited.text];

[self.delegate valuesDidChange:[NSMutableDictionary dictionaryWithObjects:changedValues forKeys:fieldKeys]];
[[self.delegate navController] popViewControllerAnimated:YES];
}
-(void)setKeyboardType:(UIKeyboardType)theType forIndex:(NSUInteger)index
{
keyboardTypes[index] = theType;
}
#pragma mark -
- (void)setFieldNames:(NSArray *)theFieldNames
{
if ([theFieldNames count] > 5)
{
NSException *e = [NSException exceptionWithName:@"Too Many Values"
reason:@"If more than five values are provided, some will be inaccessible because of the keyboard view"
userInfo:nil
]
;
[e raise];
}
[theFieldNames retain];
[fieldNames release];
fieldNames = theFieldNames;
}
- (void)setFieldValues:(NSArray *)theFieldValues
{
[theFieldValues retain];
[fieldValues release];
fieldValues = theFieldValues;

changedValues = [theFieldValues mutableCopy];
}




- (void)dealloc {
[fieldNames release];
[fieldKeys release];
[fieldValues release];
[textFieldBeingEdited release];
[super dealloc];
}

#pragma mark -
#pragma mark Table View Data Source Methods
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return 1;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [fieldNames count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *textFieldCellIdentifier = @"textFieldCellIdentifier";


UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:textFieldCellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:textFieldCellIdentifier] autorelease];
UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(10, 10, 75, 25)];
label.textAlignment = UITextAlignmentRight;
label.tag = kDefaultLabelTag;
UIFont *font = [UIFont boldSystemFontOfSize:11];
label.textColor = [UIColor colorWithRed:0.4 green:0.4 blue:0.6 alpha:1.0];
label.font = font;
[cell.contentView addSubview:label];
[label release];


UITextField *textField = [[UITextField alloc] initWithFrame:CGRectMake(94, 10, 200, 25)];
//textField.tag = kDefaultTextFieldTag;
textField.clearsOnBeginEditing = NO;
[textField setDelegate:self];
[textField addTarget:self
action:@selector(textFieldDone:)
forControlEvents:UIControlEventEditingDidEndOnExit
]
;
[cell.contentView addSubview:textField];
}
UILabel *label = (UILabel *)[cell.contentView viewWithTag:kDefaultLabelTag];
UITextField *textField = nil;
for (UIView *oneView in cell.contentView.subviews)
{
if ([oneView isMemberOfClass:[UITextField class]])
textField = (UITextField *)oneView;
}

label.text = [fieldNames objectAtIndex:[indexPath row]];
textField.text = [changedValues objectAtIndex:[indexPath row]];
textField.tag = [indexPath row];
textField.keyboardType = keyboardTypes[[indexPath row]];
return cell;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}

#pragma mark -
#pragma mark Text Field Delegate Methods
#pragma mark -
#pragma mark Table Delegate Methods
- (NSIndexPath *)tableView:(UITableView *)tableView
willSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
return nil;
}
#pragma mark -
#pragma mark Text Field Delegate Methods
- (void)textFieldDidBeginEditing:(UITextField *)textField
{
self.textFieldBeingEdited = textField;
}
- (void)textFieldDidEndEditing:(UITextField *)textField
{

[changedValues replaceObjectAtIndex:textField.tag withObject:textField.text];
}

@end


Email ThisBlogThis!Share to XShare to Facebook
Posted in Code Reuse, Controller Classes, iPhone SDK | No comments
Newer Post Older Post Home

0 comments:

Post a Comment

Subscribe to: Post Comments (Atom)

Popular Posts

  • Making OpenGL ES Screenshot
    The Bit-101 Blog has an entry that shows how to take a screenshot when using OpenGL ES . I tested this in my much-delayed particle-generato...
  • Adding CLANG to Your Build Process
    Frasier Spiers has a nifty piece this morning on using Git pre-commit hooks to automatically run the CLANG Static Analyzer. I'm not a G...
  • CLANG Static Analyzer
    If you aren't using the LLVM/Clang Static Analyzer , you really should be. The Clang Project is an attempt to write a front end for the...
  • A Little Help
    I'm having a problem with OpenGL ES, and it's keeping me from finishing my particle engine post. I was hoping someone here could see...
  • WWDC Accommodations
    Staying downtown in San Francisco is very expensive in the summertime. Bu, if you're going to WWDC, you really want to stay downtown. Yo...
  • Xcode File Templates and a Mystery
    One of the things that confuses many newcomers to Xcode is how to set it up so that your company name gets automatically filled in when you ...
  • Brain Surgery?
    Craig Hockenberry has an interesting post on his blog today about the iPhone background processing issue. Craig speaks from personal experi...
  • Book's Almost Done
    I just finished Chapter 16. I'll give it another read-over in the morning then it will go off to my writing partner for his review, then...
  • iPhone Alley
    Looks like Dave and I are going to make an appearance on the iPhone Alley Podcast next week. We're recording on Sunday night, so I woul...
  • Shuffling Arrays
    Ever want to randomize an array of items? It's a task that, for some reason, I've had to do a lot in recent programs. So, I wrote a ...

Categories

  • 3D Models
  • Ad Hoc Distribution
  • ADC
  • Address Book
  • Amazon
  • Anaglyphs
  • App Store
  • Apple
  • Apple DTS
  • Apple Store
  • Application Store
  • articles
  • Award
  • Background Processing
  • Barcodes
  • Beta
  • Blog
  • Blogger
  • Blogging
  • Blogs
  • Blogspot
  • Book project
  • Bug Reporting
  • Captain Obvious
  • Categories
  • Censorship
  • CFFoundation
  • CGAffineTransform
  • Clang Static Analyzer
  • Cocoa
  • Cocoa Touch
  • Code Reuse
  • Code Signing
  • Computer
  • conferences
  • Controller Classes
  • Core Animation
  • Daring Fireball
  • Database
  • Debugging
  • Defect
  • Delegates
  • Design Awards
  • Developer Certifications
  • Discussion Forums
  • Edit Mode
  • employment opportunities
  • Encryption
  • Enterprise
  • Errata
  • free code
  • Free software
  • Full Screen
  • Game Programming
  • Gestures
  • Getting Started
  • goof
  • Google Code
  • Google Maps
  • Gotcha
  • Help
  • HIG
  • HTTP PUT
  • Idiots
  • Idle Timer
  • Images
  • Instruments
  • Interface Builder
  • iPHone
  • iPhone Applications
  • iPhone Dev Center
  • iPhone Developers
  • iPhone OS 3.0
  • iPhone SDK
  • iPhone SDK PNG
  • iPhone Simulator
  • iPhoneSDK
  • iPod
  • Job Opportunities.
  • k
  • Key Value Observing
  • Keynote
  • KVO
  • Landscape Mode
  • Learn Cocoa
  • Learn Cocoa on the Mac
  • libxml
  • Licensing
  • Mac Developers
  • Mac OS X
  • Macworld Expo
  • Microsoft
  • NDA
  • NeHe
  • New Category
  • New Release
  • NSFileHandle
  • NSMutableArray
  • NSMutableURLRequest
  • NSXML
  • Object-Oriented Design
  • Objective-C
  • Open Source
  • OpenGL ES
  • Optimizations
  • Other blogs
  • Paired Arrays
  • Parsing
  • Particle Engine
  • Party
  • PeopleSoft
  • Performance
  • Persistence
  • Pink Screen of Death
  • Piracy
  • Pixar
  • Podcasts
  • Press Release WTF
  • Press Releases WTF
  • private APIs Google
  • Project Template
  • Properties
  • Random Numbers
  • Rant
  • Rejected
  • Resources
  • Responder Chain
  • REST
  • Reverse Engineering
  • Rumors
  • Runtime
  • Sample Code
  • Screencast
  • screenshot
  • Scroll Views
  • snippet
  • Snow Leopard.
  • SOAP
  • Sockets
  • Source
  • Splash Screen
  • SQLite
  • SQLitePersistentObjects
  • Steve Jobs
  • Steve-Note
  • Strings
  • Stupidity
  • Subversion
  • Table Views
  • Taps
  • Template
  • Tip
  • Tips
  • Tririga
  • tutorials
  • Twitter
  • UIAlertView
  • UIColor
  • UIImage
  • UIPickerView
  • UIScrollView
  • UITextField
  • UIView
  • UIWebView
  • Update
  • Utilities
  • UUID
  • Vacation
  • Version Control
  • Web Services
  • Writing
  • WTF
  • WWDC
  • Xcode
  • XML

Blog Archive

  • ►  2009 (141)
    • ►  May (14)
    • ►  April (30)
    • ►  March (48)
    • ►  February (26)
    • ►  January (23)
  • ▼  2008 (163)
    • ▼  December (46)
      • Coming Soon - My OpenGL Particle Engine
      • Updated OpenGL ES Xcode Project Template
      • Another Wavefront OBJ loader
      • PVRTC Textures & OpenGL resources from Apple
      • A Reusable Date Drill-Down Controller
      • Learn Objective-C on the Mac is Shipping
      • We broke Amazon's Top 100 Books
      • Reusable Controllers
      • Wavefront OBJ Loader - Another Update
      • Almost There...
      • Texture Coordinate Arrays or Things Nobody Tells You
      • Happy Holidays
      • OpenGL ES Extensions on the iPhone
      • Texture Mapping is Coming
      • And we have a Winner
      • More on UIAlertView and Landscape Problem
      • UIAlertView & Landscape Mode
      • More on OpenGL and Normals
      • Wavefront OBJ Loader - Normals Done...
      • Progress on the Wavefront OBJ Loader
      • Andy Ihnatko on Apple & the Macworld Expo
      • gluLookAt()
      • MacWorld 2009
      • 3D Models as C Header Files
      • OpenGL Postings
      • The Start of a WaveFront OBJ File Loader Class
      • More on Outlets - Instance Variables vs. Properties
      • Just for Fun: OpenGL Icosahedron
      • Surface Normals in OpenGL
      • NeHe Tutorials, Lesson 6 Ported to iPhone (sort of)
      • Lesson 5 Code Fixed
      • OpenGL Warning!
      • Preparations for Porting NeHe Lesson 06
      • NeHe Tutorials, Lesson 5 Ported to iPhone
      • Captain Obvious Discusses OpenGL Function Document...
      • NeHe Tutorials, Lesson 4 Ported to iPhone
      • NeHe Tutorials, Lesson 3 Ported to iPhone
      • OpenGL Project Template Redux
      • NeHe Tutorials, Lesson 2 Ported to iPhone
      • gluPerspective
      • An OpenGL Project Template for Xcode
      • Back in town
      • Next Few Days
      • Outlets - Property vs. Instance Variable
      • Speaking of Errata...
      • Happy Belated Thanksgiving
    • ►  November (25)
    • ►  October (44)
    • ►  September (2)
    • ►  August (5)
    • ►  July (2)
    • ►  June (9)
    • ►  May (2)
    • ►  April (11)
    • ►  March (17)
Powered by Blogger.

About Me

Unknown
View my complete profile