ios - How to navigate through textfields (Next / Done Buttons)

ID : 4645

viewed : 161

Tags : iosobjective-ciphoneios





Top 5 Answer for ios - How to navigate through textfields (Next / Done Buttons)

vote vote

92

In Cocoa for Mac OS X, you have the next responder chain, where you can ask the text field what control should have focus next. This is what makes tabbing between text fields work. But since iOS devices do not have a keyboard, only touch, this concept has not survived the transition to Cocoa Touch.

This can be easily done anyway, with two assumptions:

  1. All "tabbable" UITextFields are on the same parent view.
  2. Their "tab-order" is defined by the tag property.

Assuming this you can override textFieldShouldReturn: as this:

-(BOOL)textFieldShouldReturn:(UITextField*)textField {   NSInteger nextTag = textField.tag + 1;   // Try to find next responder   UIResponder* nextResponder = [textField.superview viewWithTag:nextTag];   if (nextResponder) {     // Found next responder, so set it.     [nextResponder becomeFirstResponder];   } else {     // Not found, so remove keyboard.     [textField resignFirstResponder];   }   return NO; // We do not want UITextField to insert line-breaks. } 

Add some more code, and the assumptions can be ignored as well.

Swift 4.0

 func textFieldShouldReturn(_ textField: UITextField) -> Bool {     let nextTag = textField.tag + 1     // Try to find next responder     let nextResponder = textField.superview?.viewWithTag(nextTag) as UIResponder!      if nextResponder != nil {         // Found next responder, so set it         nextResponder?.becomeFirstResponder()     } else {         // Not found, so remove keyboard         textField.resignFirstResponder()     }      return false } 

If the superview of the text field will be a UITableViewCell then next responder will be

let nextResponder = textField.superview?.superview?.superview?.viewWithTag(nextTag) as UIResponder! 
vote vote

84

There is a much more elegant solution which blew me away the first time I saw it. Benefits:

  • Closer to OSX textfield implementation where a textfield knows where the focus should go next
  • Does not rely on setting or using tags -- which are, IMO fragile for this use case
  • Can be extended to work with both UITextField and UITextView controls -- or any keyboard entry UI control
  • Doesn't clutter your view controller with boilerplate UITextField delegate code
  • Integrates nicely with IB and can be configured through the familiar option-drag-drop to connect outlets.

Create a UITextField subclass which has an IBOutlet property called nextField. Here's the header:

@interface SOTextField : UITextField  @property (weak, nonatomic) IBOutlet UITextField *nextField;   @end 

And here's the implementation:

@implementation SOTextField  @end 

In your view controller, you'll create the -textFieldShouldReturn: delegate method:

- (BOOL)textFieldShouldReturn:(UITextField *)textField {     if ([textField isKindOfClass:[SOTextField class]]) {         UITextField *nextField = [(SOTextField *)textField nextField];          if (nextField) {             dispatch_async(dispatch_get_current_queue(), ^{                 [nextField becomeFirstResponder];             });         }         else {             [textField resignFirstResponder];         }     }      return YES; } 

In IB, change your UITextFields to use the SOTextField class. Next, also in IB, set the delegate for each of the 'SOTextFields'to 'File's Owner' (which is right where you put the code for the delegate method - textFieldShouldReturn). The beauty of this design is that now you can simply right-click on any textField and assign the nextField outlet to the next SOTextField object you want to be the next responder.

Assigning nextField in IB

Moreover, you can do cool things like loop the textFields so that after the last one loses focus, the first one will receive focus again.

This can easily be extended to automatically assign the returnKeyType of the SOTextField to a UIReturnKeyNext if there is a nextField assigned -- one less thing manually configure.

vote vote

77

Here's one without delegation:

tf1.addTarget(tf2, action: #selector(becomeFirstResponder), for: .editingDidEndOnExit) tf2.addTarget(tf3, action: #selector(becomeFirstResponder), for: .editingDidEndOnExit) 

ObjC:

[tf1 addTarget:tf2 action:@selector(becomeFirstResponder) forControlEvents:UIControlEventEditingDidEndOnExit]; [tf2 addTarget:tf3 action:@selector(becomeFirstResponder) forControlEvents:UIControlEventEditingDidEndOnExit]; 

Works using the (mostly unknown) UIControlEventEditingDidEndOnExit UITextField action.

You can also easily hook this up in the storyboard, so no delegation or code is required.

Edit: actually I cannot figure out how to hook this up in storyboard. becomeFirstResponder does not seem to be a offered action for this control-event, which is a pity. Still, you can hook all your textfields up to a single action in your ViewController which then determines which textField to becomeFirstResponder based on the sender (though then it is not as elegant as the above programmatic solution so IMO do it with the above code in viewDidLoad).

vote vote

62

Here is my solution for this problem.

To solve this (and because I hate relying on tags to do stuff) I decided to add a custom property to the UITextField object. In other words I created a category on UITextField like this :

UITextField+Extended.h

@interface UITextField (Extended)  @property(retain, nonatomic)UITextField* nextTextField;  @end 

UITextField+Extended.m

#import "UITextField+Extended.h" #import <objc/runtime.h>  static char defaultHashKey;  @implementation UITextField (Extended)  - (UITextField*) nextTextField {      return objc_getAssociatedObject(self, &defaultHashKey);  }  - (void) setNextTextField:(UITextField *)nextTextField{     objc_setAssociatedObject(self, &defaultHashKey, nextTextField, OBJC_ASSOCIATION_RETAIN_NONATOMIC);  }  @end 

Now, here is how I use it :

UITextField *textField1 = ...init your textfield UITextField *textField2 = ...init your textfield UITextField *textField3 = ...init your textfield  textField1.nextTextField = textField2; textField2.nextTextField = textField3; textField3.nextTextField = nil; 

And implement the textFieldShouldReturn method :

- (BOOL)textFieldShouldReturn:(UITextField *)theTextField {      UITextField *next = theTextField.nextTextField;     if (next) {         [next becomeFirstResponder];     } else {         [theTextField resignFirstResponder];     }      return NO;  } 

I now have kind of a linked list of UITextField, each one knowing who's next in the line.

Hope it'll help.

vote vote

59

A swift extension that applies mxcl's answer to make this particularly easy (adapted to swift 2.3 by Traveler):

extension UITextField {     class func connectFields(fields:[UITextField]) -> Void {         guard let last = fields.last else {             return         }         for i in 0 ..< fields.count - 1 {             fields[i].returnKeyType = .Next             fields[i].addTarget(fields[i+1], action: "becomeFirstResponder", forControlEvents: .EditingDidEndOnExit)         }         last.returnKeyType = .Done         last.addTarget(last, action: #selector(UIResponder.resignFirstResponder), forControlEvents: .EditingDidEndOnExit)     } } 

It's easy to use:

UITextField.connectFields([field1, field2, field3]) 

The extension will set the return button to "Next" for all but the last field and to "Done" for the last field, and shift focus / dismiss the keyboard when these are tapped.

Swift < 2.3

extension UITextField {     class func connectFields(fields:[UITextField]) -> Void {         guard let last = fields.last else {             return         }         for var i = 0; i < fields.count - 1; i += 1 {             fields[i].returnKeyType = .Next             fields[i].addTarget(fields[i+1], action: "becomeFirstResponder", forControlEvents: .EditingDidEndOnExit)         }         last.returnKeyType = .Done         last.addTarget(last, action: "resignFirstResponder", forControlEvents: .EditingDidEndOnExit)     } } 

SWIFT 3: use like this -

UITextField.connectFields(fields: [field1, field2])  Extension:     extension UITextField {         class func connectFields(fields:[UITextField]) -> Void {             guard let last = fields.last else {                 return             }             for i in 0 ..< fields.count - 1 {                 fields[i].returnKeyType = .next                 fields[i].addTarget(fields[i+1], action: #selector(UIResponder.becomeFirstResponder), for: .editingDidEndOnExit)             }             last.returnKeyType = .go             last.addTarget(last, action: #selector(UIResponder.resignFirstResponder), for: .editingDidEndOnExit)         }     } 

Top 3 video Explaining ios - How to navigate through textfields (Next / Done Buttons)







Related QUESTION?