tab key to move to next field
I'm trying to do my 'object add' screen and want, ideally, to do it as a table view with a section, title, and cell for each of the 9 data fields.
MacBook, 2Gb, Mac OS X (10.5.7), MacBook, 1Gb
MacBook, 2Gb, Mac OS X (10.5.7), MacBook, 1Gb
9 data fields
// RootViewController.h
#import <UIKit/UIKit.h>
#define kTableViewSections 9
#define kTableViewRowHeight 36
#define kTextFieldTag 100
@interface RootViewController : UIViewController <UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate> {
IBOutlet UITableView *theTableView;
}
@property (nonatomic, retain) IBOutlet UITableView *theTableView;
@end
// RootViewController.m
#import "RootViewController.h"
#import "BlueAppDelegate.h"
@implementation RootViewController
@synthesize theTableView;
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return kTableViewSections;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 1;
}
- (NSString)tableView:(UITableView)tableView
titleForHeaderInSection:(NSInteger)section {
return [NSString stringWithFormat:@"Field %d", section];
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
return kTableViewRowHeight;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
CGRect textFieldRect = CGRectMake(20, 4, 280, kTableViewRowHeight-8);
UITextField *textField = [[UITextField alloc] initWithFrame:textFieldRect];
textField.backgroundColor = [UIColor lightGrayColor];
textField.font = [UIFont systemFontOfSize:22];
[textField setAutocorrectionType:UITextAutocorrectionTypeNo];
textField.tag = kTextFieldTag;
textField.delegate = self;
[cell.contentView addSubview:textField];
[textField release];
}
// find the text field and set its tag based on the current section
for (UIView *txtField in cell.contentView.subviews) {
if (txtField.tag >= kTextFieldTag) {
txtField.tag = kTextFieldTag + indexPath.section;
NSLog(@"cell: Found %@ tag=%d", txtField, txtField.tag);
break;
}
}
return cell;
}
- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath {
return nil;
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
// textField is the current first responder
NSLog(@"textFieldShouldReturn: textField.tag=%d", textField.tag);
[textField resignFirstResponder];
// scroll the next cell into view
int nextSection = (textField.tag - kTextFieldTag + 1) % kTableViewSections;
NSLog(@" nextSection=%d", nextSection);
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:nextSection];
[self.theTableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:NO];
// find the textField for the cell and make it first responder
UITableViewCell *cell = [self.theTableView cellForRowAtIndexPath:indexPath];
for (UIView *textField in cell.contentView.subviews) {
if (textField.tag >= kTextFieldTag) {
NSLog(@"return: Found %@ tag=%d", textField, textField.tag);
[textField becomeFirstResponder];
break;
}
}
return YES;
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(@"viewDidAppear");
// find the first text field and make it first responder
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
UITableViewCell *cell = [self.theTableView cellForRowAtIndexPath:indexPath];
for (UIView *textField in cell.contentView.subviews) {
if (textField.tag >= kTextFieldTag) {
NSLog(@"appear: Found %@ tag=%d", textField, textField.tag);
[textField becomeFirstResponder];
break;
}
}
}
- (void)dealloc {
[theTableView release];
[super dealloc];
}
@end
b1ueskyz wrote:
Nobody warned me yesterday was a holiday!
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
// get the current first responder
NSLog(@"textFieldShouldReturn: textField.tag=%d", textField.tag);
// identify the next cell and scroll it into view
int nSection = (textField.tag - kTextFieldTag + 1) % kTableViewSections;
NSLog(@" nSection=%d", nSection);
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:nSection];
[self.theTableView scrollToRowAtIndexPath:indexPath
atScrollPosition:UITableViewScrollPositionNone animated:YES];
// find the textField for the cell and make it first responder
UITableViewCell *cell = [self.theTableView cellForRowAtIndexPath:indexPath];
if (cell == nil) {
NSLog(@" ** cell not ready");
// set first responder in willDisplayCell
nextSection = nSection;
}
else {
UIView *textField = [cell viewWithTag:kTextFieldTag + nSection];
[textField becomeFirstResponder];
}
return YES;
}
Re: your todo's -
1. I wasn't planning on letting the focus wrap around from 8 to 0. Wondering if it's really necessary.
2. I'm wondering if, since it's a small hardcoded environment, the reuse of cells is more trouble than its worth? There will always be exactly 9 sections with exactly 1 cell each. Seems it could be much more straight forward to create 9 cells. Choose which cell to use based on indexPath.section. And get a free ride on determining the contents of that cell.
Could also help your #3 concern.
if (cell == nil) {
NSLog(@" ** cell not ready");
// set first responder in willDisplayCell
nextSection = nSection;
}
b1ueskyz wrote:
Can you post/email what you did in cellWillDisplay and how/where you use nextSection.
@interface RootViewController : UIViewController <UITableViewDataSource, UITableViewDelegate, UITextFieldDelegate> {
IBOutlet UITableView *theTableView;
int nextSection;
}
@property (nonatomic, retain) IBOutlet UITableView *theTableView;
@end
- (void)tableView:(UITableView *)tableView
willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
if (indexPath.section == nextSection) {
NSLog(@"willDisplayCell: nextSection=%d", nextSection);
UIView *textField = [cell viewWithTag:kTextFieldTag + nextSection];
[textField becomeFirstResponder];
nextSection = -1; // reset
}
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
NSLog(@"viewDidAppear");
// find the first text field and make it first responder
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
UITableViewCell *cell = [self.theTableView cellForRowAtIndexPath:indexPath];
UIView *textField = [cell viewWithTag:kTextFieldTag];
[textField becomeFirstResponder];
nextSection = -1; // initialize to a value that won't be used by willDisplayCell
}
// find the textField for the cell and make it first responder
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
if (cell == nil) {
NSLog(@" ** cell not ready");
// set first responder in willDisplayCell
nextSection = nSection;
}
else {
UIView *textField = [cell viewWithTag:kTextFieldTag + nSection];
[textField becomeFirstResponder];
}
return YES;
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
b1ueskyz wrote:
I don't think cellForRowAtIndexPath: can return nil. Or am I confusing:
tableView:cellForRowAtIndexPath: with cellForRowAtIndexPath:
// UITableViewDataSource Protocol Reference:
// Asks the data source for a cell to insert in a particular location of the table view.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
// Parameters
// tableView: A table-view object requesting the cell.
// indexPath: An index path locating a row in tableView.
// Return Value
// An object inheriting from UITableViewCell that the table view can use for the specified row.
// An assertion is raised if you return nil.
// UITableView Class Reference:
// Returns the table cell at the specified index path.
- (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath
// Parameters
// indexPath: The index path locating the row in the receiver.
// Return Value
// An object representing a cell of the table or nil if the cell is not visible or indexPath is out of range.
2. I'm wondering if, since it's a small hardcoded environment, the reuse of cells is more trouble than its worth? There will always be exactly 9 sections with exactly 1 cell each. Seems it could be much more straight forward to create 9 cells. Choose which cell to use based on indexPath.section. And get a free ride on determining the contents of that cell.
I don't think we have a choice about reusing cells. UITableView always queues cells for reuse as soon as they're no longer visible. That's how it can show thousands of cells without running out of memory. It only needs to keep as many cells as are visible at any one time. So your cells would only stay put if all 9 were always visible.
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
#import <UIKit/UIKit.h>
@interface TableViewController : UITableViewController <UITextFieldDelegate>{
int nextSection;
NSMutableArray* input;
}
@end
#import "TableViewController.h"
#define kTableViewSections 9
#define kTableViewRowHeight 36
#define kTextFieldTag 100
@implementation TableViewController
- (id)initWithStyle:(UITableViewStyle)style {
// Override initWithStyle: if you create the controller programmatically and want to perform customization that is not appropriate for viewDidLoad.
if (self = [super initWithStyle:style]) {
input = [[NSMutableArray alloc] initWithCapacity:kTableViewSections];
}
return self;
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// find the first text field and make it first responder
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
[[[cell.contentView subviews] objectAtIndex:0] becomeFirstResponder];
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell
forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == nextSection) {
UIView *textField = [cell viewWithTag:kTextFieldTag + nextSection];
[textField becomeFirstResponder];
nextSection = -1; // reset
}
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
return [NSString stringWithFormat: @"Section %d", section];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return kTableViewSections;
}
// Customize the number of rows in the table view.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 1;
}
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString* CellIdentifier = @"Cell";
UITextField* textField;
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
// create a new cell
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
// set up a text field
CGRect textFieldRect = CGRectMake(20, 4, 280, kTableViewRowHeight-8);
textField = [[UITextField alloc] initWithFrame:textFieldRect];
textField.backgroundColor = [UIColor lightGrayColor];
textField.font = [UIFont systemFontOfSize:22];
[textField setAutocorrectionType:UITextAutocorrectionTypeNo];
textField.tag = kTextFieldTag;
textField.delegate = self;
// add text field to cell's view
[cell.contentView addSubview:textField];
[textField release];
}
// find the cell's text field and set its tag based on the current section
textField = [[cell.contentView subviews] objectAtIndex:0];
if (textField.tag >= kTextFieldTag) {
textField.tag = kTextFieldTag + indexPath.section;
}
// clear text field contents
textField.text = @"";
return cell;
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
/////////////////////////////////////////////////
// The user pressed 'Rtn' ///////////////////////
/////////////////////////////////////////////////
// get the contents of the just completed textField ///////
///////////////////////////////////////////////////////////
NSLog(@"contents of field %d with rtn pressed: %@", textField.tag, textField.text);
// identify the next cell and scroll it into view /////////
///////////////////////////////////////////////////////////
// ( current tag - 100 + 1 ) mod 9
int nSection = (textField.tag - kTextFieldTag + 1) % kTableViewSections;
// use the fact every section has 1 row [0]
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:nSection];
[self.tableView scrollToRowAtIndexPath:indexPath
atScrollPosition:UITableViewScrollPositionTop
animated:YES];
// find the textField for this scrolled cell and make it first responder //////
///////////////////////////////////////////////////////////////////////////////
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
if (cell == nil) {
NSLog(@" ** cell not ready");
// set first responder in willDisplayCell
nextSection = nSection;
}
else {
NSLog(@"section: %d and row: %d", indexPath.section, indexPath.row);
UIView *textField = [cell viewWithTag:kTextFieldTag + nSection];
[textField becomeFirstResponder];
}
return YES;
}
@end
#import "TableViewController.h"
#define kTableViewSections 9
#define kTableViewRowHeight 36
#define kTextFieldTag 100
@implementation TableViewController
- (id)initWithStyle:(UITableViewStyle)style {
if (self = [super initWithStyle:style]) {
input = [[NSMutableArray alloc] init];
for ( int i = 0; i < kTableViewSections; i++ ) {
[input addObject: @""];
}
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]
initWithBarButtonSystemItem:UIBarButtonSystemItemSave
target:self action:@selector(save_Clicked:)];
}
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
// find the first text field and make it first responder
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
[[[cell.contentView subviews] objectAtIndex:0] becomeFirstResponder];
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell
forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.section == nextSection) {
UIView *textField = [cell viewWithTag:kTextFieldTag + nextSection];
[textField becomeFirstResponder];
nextSection = -1; // reset
}
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
return [NSString stringWithFormat: @"Section %d", section];
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return kTableViewSections;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return 1;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString* CellIdentifier = @"Cell";
UITextField* textField;
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
// set up a text field
CGRect textFieldRect = CGRectMake(20, 4, 280, kTableViewRowHeight-8);
textField = [[UITextField alloc] initWithFrame:textFieldRect];
textField.backgroundColor = [UIColor lightGrayColor];
textField.font = [UIFont systemFontOfSize:22];
[textField setAutocorrectionType:UITextAutocorrectionTypeNo];
textField.tag = kTextFieldTag;
textField.delegate = self;
// add text field to cell's view
[cell.contentView addSubview:textField];
[textField release];
}
// find the cell's text field and set its tag based on the current section
textField = [[cell.contentView subviews] objectAtIndex:0];
if (textField.tag >= kTextFieldTag) {
textField.tag = kTextFieldTag + indexPath.section;
}
textField.text = [input objectAtIndex:indexPath.section];
return cell;
}
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
[input replaceObjectAtIndex:(textField.tag - kTextFieldTag) withObject:textField.text];
int nSection = (textField.tag - kTextFieldTag + 1) % kTableViewSections;
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:nSection];
[self.tableView scrollToRowAtIndexPath:indexPath
atScrollPosition:UITableViewScrollPositionTop
animated:YES];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
if (cell == nil) {
NSLog(@" ** cell not ready");
// the first responder will get set in willDisplayCell
nextSection = nSection;
}
else {
UIView *tField = [cell viewWithTag:kTextFieldTag + nSection];
[tField becomeFirstResponder];
}
return YES;
}
- (void)save_Clicked:(id)sender {
NSLog(@"save the data in the array");
UIView* fResponder = [[[UIApplication sharedApplication] keyWindow] performSelector:@selector(firstResponder)];
[fResponder resignFirstResponder];
}
- (void)dealloc {
[input release];
[super dealloc];
}
@end
b1ueskyz wrote:
Got it!
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];
[input replaceObjectAtIndex:(textField.tag - kTextFieldTag) withObject:textField.text];
int nSection = (textField.tag - kTextFieldTag + 1) % kTableViewSections;
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:nSection];
if ([self.tableView cellForRowAtIndexPath:indexPath] == nil) // <-- #1
// cell is not visible - scroll it into view
[self.tableView scrollToRowAtIndexPath:indexPath
atScrollPosition:UITableViewScrollPositionTop animated:YES]; // <-- #2
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
if (cell == nil) {
NSLog(@" ** cell not ready");
// the first responder will get set in willDisplayCell
nextSection = nSection;
// set temporary first responder to keep the keyboard up
[extraTextField becomeFirstResponder]; // <-- #3
}
else {
UIView *tField = [cell viewWithTag:kTextFieldTag + nSection];
[tField becomeFirstResponder];
}
return YES;
}
tab key to move to next field