How to navigate through textfields (Next / Done Buttons)
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:
- All "tabbable"
UITextField
s are on the same parent view. - 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!
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
andUITextView
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.
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.
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
).