27

I use the "Next" value for the "Return Key" to get the Next button in place of the Done button, but (obviously) pressing it doesn't automatically move to the next UITextField in my view.

What's the right way to do this? On a larger topic, what are some tips for properly building forms in the iPhone SDK?

3n.
  • 1,671
  • 2
  • 17
  • 15

6 Answers6

36

Make some object the first text field's delegate, and implement the - (BOOL)textFieldShouldReturn:(UITextField *)textField method; in that, call the second text field's -becomeFirstResponder. Returning YES from that will make the text field perform its default behavior for the return button – I think that's generally sending its action message. If you don't have anything added as a target of that action, it doesn't really matter what you return.

Noah Witherspoon
  • 57,021
  • 16
  • 130
  • 131
33

To build on Noah's answer, if you have a lot of textfields and don't feel like having a bunch of if's, you could do it this way:

- (BOOL)textFieldShouldReturn:(UITextField *)textField
{
    //[[self.view viewWithTag:textField.tag+1] becomeFirstResponder];

    UIView *view = [self.view viewWithTag:textField.tag + 1];     
    if (!view)         
          [textField resignFirstResponder];     
    else         
          [view becomeFirstResponder];
    return YES;
}

Once you tag every textfield starting at any number, as long as they're tagged sequentially, in storyboard or in code, it should work.

Mihir Mehta
  • 13,743
  • 3
  • 64
  • 88
Philippe Sabourin
  • 8,066
  • 3
  • 31
  • 46
  • Great solution! One (minor) suggested tweak - test for viewWithTag == nil: `UIView *view = [self.view viewWithTag:textField.tag + 1]; if (!view) [textField resignFirstResponder]; else [view becomeFirstResponder];` – CharlesA Sep 25 '13 at 09:20
2

For Swift:

func textFieldShouldReturn(textField: UITextField) -> Bool {
//your collection of textfields
    guard let i = textFields.indexOf(textField) else { return false }
    if i + 1 < textFields.count {
        textFields[i + 1].becomeFirstResponder()
        return true
    }
    textField.resignFirstResponder()
    return true
}
aBikis
  • 323
  • 4
  • 16
1

This seems to work quite well and doesn't require the tag system many are suggesting. There are 2 things to note with this solution though:

  • All the UITextFields must be in the same UIView (have the same superview).
  • The UITextFields need to be in the right order in the interface builder.

    -(BOOL)textFieldShouldReturn:(UITextField *)textField    {
       /*
       *  1. Loop through the textfield's superview
       *  2. Get the next textfield in the superview  
       *  3. Focus that textfield
       */
    
       UIView *superView = [textField superview];
       BOOL foundCurrent = false;
       for (UITextField *tf in superView.subviews) {
          // Set focus on the next textfield
          if (foundCurrent) {
             [tf becomeFirstResponder];
             return NO;
          }
    
         //Find current textfield
         if ([tf isEqual:textField]) {
             foundCurrent = true;
          }
       }
    
       return YES;
    }    
    
Weeman360
  • 83
  • 6
1

I don't like to deal with tag so here is my solution. Create an IBOutletCollection of all your textFields in your ViewController, drag to connect your textFields in order from top to bottom.

@interface ViewController () <UITextFieldDelegate>
@property (strong, nonatomic) IBOutletCollection(UITextField) NSArray *allTextFields;
@end

In viewDidLoad set your textFields delegate. (Or set it in storyboard).

for (VVTextField *tf in self.allTextFields) {
    tf.delegate = self;
}

Then implement UITextField Delegate

#pragma mark - UITextField Delegate

- (BOOL)textFieldShouldReturn:(UITextField *)textField {
    NSUInteger currentIndex = [self.allTextFields indexOfObject:textField];
    NSUInteger nextIndex = currentIndex+1;
    if (nextIndex < self.allTextFields.count) {
        [[self.allTextFields objectAtIndex:nextIndex] becomeFirstResponder];
    } else {
        [[self.allTextFields objectAtIndex:currentIndex] resignFirstResponder];
    }
    return YES;
}
imObjCSwifting
  • 743
  • 1
  • 12
  • 34
  • 1
    This may work but the objects in your `IBOutletCollection` array are not guaranteed to be in the correct order. – JAL Apr 01 '16 at 19:35
  • @JAL Has a great point. You should consider edit your answer or map the values some how. This may cause unexpected behavior. – Roi Mulia Feb 14 '17 at 10:08
0

I've been struggling with this issue too...and as a result I've created small library for handling multiple textfields. You can find it on github GNKeyboardAwareScrollView#GNTextFieldsManager.

You can initialise it with array of textfields:

NSArray *myTextFields = @[...]; // the order of array matters!
GNTextFieldsManager *manager = [[GNTextFieldsManager alloc] initWithTextFields:myTextFields];

Or by specifying parent view (and setting tags for all views):

GNTextFieldsManager *manager = [[GNTextFieldsManager alloc] initWithView:self.view];

Hope i'll be useful for somebody :)

JakubKnejzlik
  • 6,363
  • 3
  • 40
  • 41