tab key to move to next field

Under iPhone OS 2.x or 3.x, is there a way to cause the keyboard focus to switch to the next field? Like hitting the tab key under Mac OSX.

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

Posted on Jul 3, 2009 6:34 PM

Reply
19 replies

Jul 3, 2009 7:35 PM in response to b1ueskyz

Erm.. The questions in my last response don't make any sense. Can I try again?

Do you want something like the Edit Address screen in the Contacts app? I.e., operation similar to editing the Street, Street, City fields, which move the focus when Return is tapped? Would there be one or more UITextFields on each cell of a table view?

Hope I'm making more sense this time. - R

Jul 3, 2009 9:52 PM in response to b1ueskyz

Yes... it's a typical set of address/contact fields. All text, one UITextField per cell. And I'm shoving the scrollview up out of the way so the keyboard can be up for the whole entry op. At the conclusion, I'll use a Done or Save button in the right side of navbar to end entry. I didn't like the examples I saw that transitioned to a new screen for each field selected. Too much superfluous movement.

Jul 4, 2009 4:20 AM in response to b1ueskyz

Hi Phil - I've been playing with a test bed, trying to get it to do what you described. It's close to finished, but I need to quit now and won't be able to get back to it until Sunday. Here's what I've got so far. Maybe you'll be able to finish it before I do:

// 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

There are three jobs on my todo list:

1) Bug fix: In textFieldShouldReturn, I had to turn off the animation for scrollToRowAtIndexPath because when the focus wanted to wrap from section 8 back to section 0, the cell for section 0 wasn't ready. I think we need to wait for the scroll animation to end before trying to reset the focus;

2) Bug fix: The contents of each text field needs to be saved as soon as it loses focus. The saved text then needs to refresh the section when it next becomes visible. Right now you'll see, for example. the text from section 1 reappears in section 6 when that section becomes visible. Of course, this is because the cell from section 1 has been reused for section 6. So the contents of each reused text field needs to be cleared and refreshed from a cache in cellForRowAtIndexPath;

3) Clean up: I use a for loop to find each cell's text field. I can't simply use viewWithTag because the tag is within a range instead of being a constant. There are three of these loops, so that code wants to be in a helper method. Of course, if you're using a subclassed cell, you won't need those loops, since the text field can be a property of the cell.

I'm sure there willl be other odds and ends to clean up, but I think the above structure might do the job for you. Please let me know if you have things working before Sunday afternoon.

- Ray

Jul 4, 2009 1:58 PM in response to RayNewbie

Wow... not what I was really expecting. I was just looking for "Had I missed some setting to make the keyboard behave as I wanted with regard to advancing to the next field". But, hey, this looks fun and interesting.

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.

As it turns out, I do have 1 of the cells I'd like slightly different from the others. But the other 8 would be identical except for the actual text contained in the textField. Each cell's contentView will always contain exactly 1 textField.

Thanks for all the effort. Will be playing with it all day. Will advise what I've accomplished by noon Sunday.
-Phil

Jul 6, 2009 1:47 AM in response to b1ueskyz

b1ueskyz wrote:
Nobody warned me yesterday was a holiday!

I would've mentioned it, Phil, but I wasn't sure folks down in Paso Robles knew about the 4th. Guess you're not as far off of 101 as I remember. I didn't have the opportunity to miss the celebration. My kids scored enough ordnance to keep Watsonville safe for democracy until next summer. I don't know why you couldn't have seen the Grand Finale from your place.

Anyway I'm back working on your text fields again. I fixed the items on my todo list, but now I've run into a problem that might be organic.. not sure there's an easy workaround. It seems that when a text field gains the focus (becomes first responder), the table view somehow knows this and automatically scrolls the containing cell into the middle of the visible area. This happens despite sending any scroll-to messages and so far I have no idea whether this behavior can be turned off.

In other words, suppose cell 5 is in the middle, with cell 4 visible above and cell 6 visible below. When you hit the return key, this code runs:

- (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;
}

UITableViewScrollPositionNone should scroll the table only if the next cell isn't visible. So if you edit the text field in cell 5, then hit return, the table view shouldn't scroll if cell 6 is already visible. That's what I think the user would like. I.e. I think it's distracting if a form scrolls when it doesn't need to. But as soon as 6 gets the focus it's scrolled into the middle of the visible table regardless of where it was to begin with.

If there's no good way to prevent this extra scrolling, I might prefer to put the text fields onto an ordinary UIScrollView instead of UITableView. Hopefully the scroll view isn't as smart as the table view and won't care where the focus is.
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.

No, it's not necessary. Many similar interfaces wrap around, so I thought you might want that. Remember there's no other way to return to a field from the keyboard. But it's certainly not necessary.

Btw, the above code indicates how I solved the wrap around problem. If the return from cellForRowAtIndexPath is nil, we assume the cell we want isn't visible yet. In that case we save it's section no. in an ivar and wait to set the focus until that cell comes up in willDisplayCell.
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.

But the memory management isn't nearly as complicated in an ordinary scroll view. So if you chose that option, the 9 text fields would always be in the same place relative to the content view.
Could also help your #3 concern.

#3 went away when I realized cellForRowAtIndexPath is the only place that loop is needed. When I came back to the code with a (relatively) clear mind, I saw that we know exactly what tag we're looking for at every other point, since it will always correspond to the index path (duh!).

I think the option of using UIScrollView becomes attractive if you're not using a grouped table view. If you want the look of a grouped table view--the rounded borders like Contacts->New Contact->Add New Address--then it might be worth the effort to solve the problem discussed above. But if you were planning on a plain table view for a more linear look, you might want to consider an ordinary scroll view.

- Ray

Jul 6, 2009 9:11 PM in response to RayNewbie

Well, got your code plugged into my Sandbox and wow... very close!
Ran into a little snag... nextSection is not defined and I don't see the connection to 'willDisplayCell'

if (cell == nil) {
NSLog(@" ** cell not ready");
// set first responder in willDisplayCell
nextSection = nSection;
}


To your notes above:
• I don't think the extra scrolling looks bad at all. In fact I think it looks better than if sometimes it scrolled and sometimes the cursor just jumped to another field without scrolling.
• Your right the user will expect it to scroll from 8 to 0.
• I really prefer the grouped display look so , yes, I'd like to make this work in a tableView rather than a scrollView.

Let me play with this a bit and see what I can do with it. Can you post/email what you did in cellWillDisplay and how/where you use nextSection.

Thanks.
-Phil

Jul 6, 2009 10:08 PM in response to b1ueskyz

b1ueskyz wrote:
Can you post/email what you did in cellWillDisplay and how/where you use nextSection.

nextSection is a private ivar that's set to indicate the section that wants the focus; i.e. it's used to leave a message for cellWillDisplay:

@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
}

I'm not sure this use of willDisplayCell is the best way to set the focus to a field that isn't visible when the user hits return. But it's the best I've done with that issue so far.

- Ray

Jul 6, 2009 11:54 PM in response to RayNewbie

I've stared at this until my eyes are crossed... how can it ever take the cell==nil path?
I don't think cellForRowAtIndexPath can return nil but watching the logs, it apparently does. Can you enlighten me?

Thanks.
-Phil


// 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;

Jul 7, 2009 8:18 AM in response to just.do.it

The little snippet of code calls:

UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];

And I don't think cellForRowAtIndexPath: can return nil. Or am I confusing:
tableView:cellForRowAtIndexPath: with cellForRowAtIndexPath: Hmmmmmm...
Just noticed that subtle difference. They are 2 diff functions. I tell you that RayNewbie is so tricky!

Jul 8, 2009 1:05 AM in response to b1ueskyz

b1ueskyz wrote:
I don't think cellForRowAtIndexPath: can return nil. Or am I confusing:
tableView:cellForRowAtIndexPath: with cellForRowAtIndexPath:

Yeah, I never noticed how confusing those names can be.

// 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.

To make matters worse, I often use the second part of the signature to refer to delegate or data source methods, since the first part is usually the same for all the methods in the protocol. So I may have used cellForRowAtIndexPath for either of the two distinct methods. I really never noticed that the strings were identical.

Anyway I hope the confusion is cleared up now. The method used in textFieldShouldReturn to address a cell returns nil if the cell is not visible, which generally means there's currently no cell at the indexPath in question.

I'm wondering if another issue has been confusing you in addition to the method names... Have you grokked the way the table view shuffles cells around? Some of your comments suggest that you expect to find a cell at each valid index path. This was the subject of an earlier discussion in this thread:
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.

Does the above make sense now? The important point is unless all the cells in a table are visible, there will be many index paths (usually most) which are just logical addresses. I.e. there's no subview of any kind at that index--no cell, no nothing.

So for example, if only sections 6, 7 and 8 are visible, when we try to get the address of the cell at the index path for section 0, row 0:

UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];

What would you expect the method to return? _There's no cell at that indexPath._ The cell that used to be there may be in the reusable cells queue, or it might be in one of the currently visible locations.

So what can we do when we want to set the focus to the cell at 0, 0, but there's no such cell? That's the purpose of the nextSection ivar and the code in willDisplayCell. After we ask the table view to scroll until section 0 is visible, we know that sooner or later the table view will need to populate that index position. As soon as a cell is ready at that index, the table view will pass indexPath 0, 0 to the willDisplayCell delegate method. If the delegate method finds that indexPath matches nextSection, it knows we want the cell at that location to get the focus.

- Ray

Jul 11, 2009 3:19 PM in response to RayNewbie

Sorry... I got waylaid for a couple days. Funny how old projects pop up now and then. Like Michael Corleone said: "Just when I thought I was out... they pull me back in."

I did some tweaking to your code some just because I'm using a UITableViewController instead of a UITableView. It's to the point where it works as expected the first time thru for the user but if you wrap around and go again, it gets confused. The 9th cell doesn't clear and it doesn't get focus. I can't find the issue.

My user testing is type a,b,c,... into the cells thru 'i' goes into the 9th cell. Then it wraps around and you get blank cells from 0 on. I keep typing j,k,l,... into the cells and when the 9th one comes around for the second time, it still has 'i' in it and won't take the focus. Although if you look in the Log statements, if you type an 'r' for the 9th cell it gets registered in the array correctly but not on the screen. Hope that makes sense!

-Phil

If you're of a mind, the relevent code is below.


#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

Jul 25, 2009 5:35 PM in response to b1ueskyz

Got it!
A slight quirky behavior on the part of the keyboard but the functionality is what I have been looking for. In case anyone else is interested, the implementation code is below. Many thank to RayNewbie for most of the work. The last change I made was the first line of textFieldShouldReturn: method. That seems to have done it.


#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

Jul 26, 2009 12:23 AM in response to b1ueskyz

b1ueskyz wrote:
Got it!


- (BOOL)textFieldShouldReturn:(UITextField *)textField {
[textField resignFirstResponder];

Great catch, Phil!!! I've been trying to understand what was going on with that last field (It didn't need to be no. 8 either; when I shortened the table to 8 sections, no. 7 wouldn't get the focus after the first time through). I think the behavior had something to do with taking first responder away from a text field that was no longer in the view hierarchy. By the time willDisplayCell ran, the last cell had been removed from its superview and placed in the reusable cell queue. Maybe that detached cell got wonky because its text field shouldn't have been asked to resignFirstResponder when it didn't have a superview. Or maybe removing any first responder from its superview is a no no. Either theory would explain why your fix works, since the text field now resigns first responder before the cell is removed from its superview. Anyway, you deserve a big green star for that fix!!

With that mystery solved, i was inspired to try some other ideas to enable an animated scroll without side effects:

- (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;
}

#2: Change #'s 1 and 3 are intended to allow animation, so it's turned on here;

#1: As we discussed, the table view wants to scroll any visible first responder into the center of the visible area regardless of what we might prefer. This was messing up the animation because the FR cell was centered before the scrollToRow animation was finished. I.e. the cell wasn't located at the end point of the animation, which is usually always a bad thing. Since there's no need to call scrollToRow for a cell that's already visible, we just test for visibility here;

#3: Since we now have no first responder until willDisplayCell runs, when the scroll from row 8 back to row 0 is animated, the keyboard will bounce down and back up during the animation. There must be a better way to correct this, but for now I just added an extra text view to keep the keyboard up.

I don't think we should ask a detached text view to accept first responder, That might bring back the same kind of bug you just fixed. So I added an IBOutlet UITextField *extraTextField ivar that's connected to a No Border Style Text Field object in IB. I made my testbed by placing a 320 x 244 table view on top of a 320 x 460 content view. The table view ends where the top of the keyboard will be, and there's lots of room for the extra field under the keyboard. If you aren't configured that way, maybe you could just add the extra field directly to the window, or hide it on another view by setting the frame origin off the screen.

Anyway the scrolling looks much better now at this end, so see if you like it.

Dec 28, 2009 5:20 AM in response to RayNewbie

Does this code work for you guys when you are pressing forward Next button very fast?

I have similar code (I think!), and I find that the table view goes FUBAR when Next button is pressed very fast.
- It will show table view rendered with incomplete text fields
- It will make so that no field has focus

I have tried different things, but it really seems that table view was not cut out to be used like this.. Don't know any better easily used widgets either.

This thread has been closed by the system or the community team. You may vote for any posts you find helpful, or search the Community for additional answers.

tab key to move to next field

Welcome to Apple Support Community
A forum where Apple customers help each other with their products. Get started with your Apple Account.