Scenario: I have 4 UITextFields that only accept 1 character. Easy.
Problem: After I enter the 1 character, I want the next TextField to become active automatically with
I arrived at a solution by modifying some code I found here: http://www.thepensiveprogrammer.com/2010/03/customizing-uitextfield-formatting-for.html
First set the your view controller to be the delegate of the textfields.
Then do something like this:
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
BOOL shouldProcess = NO; //default to reject
BOOL shouldMoveToNextField = NO; //default to remaining on the current field
int insertStringLength = [string length];
if(insertStringLength == 0){ //backspace
shouldProcess = YES; //Process if the backspace character was pressed
}
else {
if([[textField text] length] == 0) {
shouldProcess = YES; //Process if there is only 1 character right now
}
}
//here we deal with the UITextField on our own
if(shouldProcess){
//grab a mutable copy of what's currently in the UITextField
NSMutableString* mstring = [[textField text] mutableCopy];
if([mstring length] == 0){
//nothing in the field yet so append the replacement string
[mstring appendString:string];
shouldMoveToNextField = YES;
}
else{
//adding a char or deleting?
if(insertStringLength > 0){
[mstring insertString:string atIndex:range.location];
}
else {
//delete case - the length of replacement string is zero for a delete
[mstring deleteCharactersInRange:range];
}
}
//set the text now
[textField setText:mstring];
[mstring release];
if (shouldMoveToNextField) {
//
//MOVE TO NEXT INPUT FIELD HERE
//
}
}
//always return no since we are manually changing the text field
return NO;
}
The following is for Swift 5 and handles textfields as an array instead of individual fields.
import UIKit
class MyViewController: UIViewController, UITextFieldDelegate {
@IBOutlet var digitFields: [UITextField]!
override func viewDidLoad() {
super.viewDidLoad()
digitFields.forEach {
configureDigitField($0)
}
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
digitFields[0].becomeFirstResponder()
}
fileprivate func configureDigitField(_ digitField: UITextField) {
digitField.delegate = (self as UITextFieldDelegate)
digitField.addTarget(self, action: #selector(self.textFieldDidChange(textField:)), for: UIControl.Event.editingChanged)
}
// Move to next field in digit fields if the value is populated
@objc fileprivate func textFieldDidChange(textField: UITextField) {
if textField.text?.count == 1 {
let remaining = digitFields.filter { $0.text?.count == 0 }
if remaining.count > 0 {
remaining[0].becomeFirstResponder()
} else {
digitFields.forEach { $0.resignFirstResponder() }
}
}
}
Results in:
This is dependent on the textfields being grouped in an array. This can be achieved in interface builder by configuring the collection of fields in the Outlet configuration screen:
which can be reached from the view controller properties on the last tab item
Note that you need to manually add the
@IBOutlet var digitFields: [UITextField]!
to your view controller before you can add the text fields to it.
Summary of Code Behaviour
Although it is an old question, I just came cross it and I came with simpler solution. assuming we doing this for pass code so each box (UITextField) max length is one char.
- (BOOL) textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{
if (![string isEqualToString:@""]) {
textField.text = string;
if ([textField isEqual:self.txtField1]) {
[self.txtField2 becomeFirstResponder];
}else if ([textField isEqual:self.txtField2]){
[self.txtField3 becomeFirstResponder];
}else if ([textField isEqual:self.txtField3]){
[self.txtField4 becomeFirstResponder];
}else{
[textField resignFirstResponder];
}
return NO;
}
return YES;
}
-(BOOL)textFieldShouldBeginEditing:(UITextField *)textField{
if (textField.text.length > 0) {
textField.text = @"";
}
return YES;
}
UPDATED CODE FOR SWIFT 3
@IBOutlet weak var tf1: UITextField!
@IBOutlet weak var tf2: UITextField!
@IBOutlet weak var tf3: UITextField!
@IBOutlet weak var tf4: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
tf1.delegate = self
tf2.delegate = self
tf3.delegate = self
tf4.delegate = self
tf1.addTarget(self, action: #selector(self.textFieldDidChange(textField:)), for: UIControlEvents.editingChanged)
tf2.addTarget(self, action: #selector(self.textFieldDidChange(textField:)), for: UIControlEvents.editingChanged)
tf3.addTarget(self, action: #selector(self.textFieldDidChange(textField:)), for: UIControlEvents.editingChanged)
tf4.addTarget(self, action: #selector(self.textFieldDidChange(textField:)), for: UIControlEvents.editingChanged)
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(true)
tf1.becomeFirstResponder()
}
func textFieldDidChange(textField: UITextField){
let text = textField.text
if text?.utf16.count==1{
switch textField{
case tf1:
tf2.becomeFirstResponder()
case tf2:
tf3.becomeFirstResponder()
case tf3:
tf4.becomeFirstResponder()
case tf4:
tf4.resignFirstResponder()
default:
break
}
}else{
}
}
I know this is a very old question, but here's my approach for allowing a single numeric value only across four UITextFields, and automatically 'tabbing' to the next one (pin1-pin4 each represents a PIN number digit lol, and are retained as properties):
-(BOOL)textFieldShouldReturn:(UITextField*)textField;
{
if (textField == pin1)
{
[pin2 becomeFirstResponder];
}
else if (textField == pin2)
{
[pin3 becomeFirstResponder];
}
else if (textField == pin3)
{
[pin4 becomeFirstResponder];
}
return NO;
}
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
// This allows numeric text only, but also backspace for deletes
if (string.length > 0 && ![[NSScanner scannerWithString:string] scanInt:NULL])
return NO;
NSUInteger oldLength = [textField.text length];
NSUInteger replacementLength = [string length];
NSUInteger rangeLength = range.length;
NSUInteger newLength = oldLength - rangeLength + replacementLength;
// This 'tabs' to next field when entering digits
if (newLength == 1) {
if (textField == pin1)
{
[self performSelector:@selector(setNextResponder:) withObject:pin2 afterDelay:0.2];
}
else if (textField == pin2)
{
[self performSelector:@selector(setNextResponder:) withObject:pin3 afterDelay:0.2];
}
else if (textField == pin3)
{
[self performSelector:@selector(setNextResponder:) withObject:pin4 afterDelay:0.2];
}
}
//this goes to previous field as you backspace through them, so you don't have to tap into them individually
else if (oldLength > 0 && newLength == 0) {
if (textField == pin4)
{
[self performSelector:@selector(setNextResponder:) withObject:pin3 afterDelay:0.1];
}
else if (textField == pin3)
{
[self performSelector:@selector(setNextResponder:) withObject:pin2 afterDelay:0.1];
}
else if (textField == pin2)
{
[self performSelector:@selector(setNextResponder:) withObject:pin1 afterDelay:0.1];
}
}
return newLength <= 1;
}
- (void)setNextResponder:(UITextField *)nextResponder
{
[nextResponder becomeFirstResponder];
}
Swift 4.x
textField.addTarget(self, action: #selector(self.textFieldDidChange(textField:)), for: .editingChanged)
@objc func textFieldDidChange(textField: UITextField)
{
let text = textField.text
if text?.utf16.count == 1 {
switch textField {
case txtOtpNumber1:
txtOtpNumber2.becomeFirstResponder()
case txtOtpNumber2:
txtOtpNumber3.becomeFirstResponder()
case txtOtpNumber3:
txtOtpNumber4.becomeFirstResponder()
case txtOtpNumber4:
txtOtpNumber4.resignFirstResponder()
default:
break
}
} else {
switch textField {
case txtOtpNumber4:
txtOtpNumber3.becomeFirstResponder()
case txtOtpNumber3:
txtOtpNumber2.becomeFirstResponder()
case txtOtpNumber2:
txtOtpNumber1.becomeFirstResponder()
case txtOtpNumber1:
txtOtpNumber1.resignFirstResponder()
default:
break
}
}
}
PS. Chetan's answer updated for the current Swift.