I have a Verification ViewController
, I get 4 digit verification code by SMS and I need to enter those code to login, I have created the ViewController
Swift 4
Inspired by @Anurag Soni and @Varun Naharia answers
Variant A
extension EnterConfirmationCodeTextField: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let textFieldCount = textField.text?.count else { return false }
// Сlosure
let setValueAndMoveForward = {
textField.text = string
let nextTag = textField.tag + 1
if let nextResponder = textField.superview?.viewWithTag(nextTag) {
nextResponder.becomeFirstResponder()
}
}
// Сlosure
let clearValueAndMoveBack = {
textField.text = ""
let previousTag = textField.tag - 1
if let previousResponder = textField.superview?.viewWithTag(previousTag) {
previousResponder.becomeFirstResponder()
}
}
if textFieldCount < 1 && string.count > 0 {
setValueAndMoveForward()
if textField.tag == 4 {
print("Do something")
}
return false
} else if textFieldCount >= 1 && string.count == 0 {
clearValueAndMoveBack()
return false
} else if textFieldCount >= 1 && string.count > 0 {
let nextTag = self.tag + 1
if let previousResponder = self.superview?.viewWithTag(nextTag) {
previousResponder.becomeFirstResponder()
if let activeTextField = previousResponder as? UITextField {
activeTextField.text = string
}
}
return false
}
return true
}
}
Variant B (a little bit another behavior):
extension EnterConfirmationCodeTextField: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let textFieldCount = textField.text?.count else { return false }
// Сlosure
let setValueAndMoveForward = {
textField.text = string
let nextTag = textField.tag + 1
if let nextResponder = textField.superview?.viewWithTag(nextTag) {
nextResponder.becomeFirstResponder()
}
}
// Сlosure
let clearValueAndMoveBack = {
textField.text = ""
let previousTag = textField.tag - 1
if let previousResponder = textField.superview?.viewWithTag(previousTag) {
previousResponder.becomeFirstResponder()
}
}
if textFieldCount < 1 && string.count > 0 {
setValueAndMoveForward()
if textField.tag == 4 {
print("Do something")
}
return false
} else if textFieldCount >= 1 && string.count == 0 {
clearValueAndMoveBack()
return false
} else if textFieldCount >= 1 {
setValueAndMoveForward()
return false
}
return true
}
}
Also, I implemented this feature:
In the case where the last textFiled is empty, I just want to switch to the previous textFiled. I tried all this methods. But as for me the method below more elegant and works like a charm:
Variant A
class EnterConfirmationCodeTextField: UITextField {
// MARK: Life cycle
override func awakeFromNib() {
super.awakeFromNib()
delegate = self
}
// MARK: Methods
override func deleteBackward() {
super.deleteBackward()
let previousTag = self.tag - 1
if let previousResponder = self.superview?.viewWithTag(previousTag) {
previousResponder.becomeFirstResponder()
if let activeTextField = previousResponder as? UITextField {
if let isEmpty = activeTextField.text?.isEmpty, !isEmpty {
activeTextField.text = String()
}
}
}
}
}
Variant B (a little bit another behavior):
class EnterConfirmationCodeTextField: UITextField {
// MARK: Life cycle
override func awakeFromNib() {
super.awakeFromNib()
delegate = self
}
// MARK: Methods
override func deleteBackward() {
super.deleteBackward()
let previousTag = self.tag - 1
if let previousResponder = self.superview?.viewWithTag(previousTag) {
previousResponder.becomeFirstResponder()
}
}
}
Assign EnterConfirmationCodeTextField
for each of your textFields and set they appropriate tag
value.
It can be achieve using UITextField delegate & by setting Tag for each Textfield in increasing order (say 1 - 4), below is the delegate handler to solve the issue.
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
// On inputing value to textfield
if (textField.text?.characters.count < 1 && string.characters.count > 0){
let nextTag = textField.tag + 1;
// get next responder
var nextResponder = textField.superview?.viewWithTag(nextTag);
if (nextResponder == nil){
nextResponder = textField.superview?.viewWithTag(1);
}
textField.text = string;
nextResponder?.becomeFirstResponder();
return false;
}
else if (textField.text?.characters.count >= 1 && string.characters.count == 0){
// on deleteing value from Textfield
let previousTag = textField.tag - 1;
// get next responder
var previousResponder = textField.superview?.viewWithTag(previousTag);
if (previousResponder == nil){
previousResponder = textField.superview?.viewWithTag(1);
}
textField.text = "";
previousResponder?.becomeFirstResponder();
return false;
}
return true;
}
swift 2.3
class BankDepositsWithOTPVC: UIViewController {
let limitLength = 1
override func viewDidLoad() {
super.viewDidLoad()
}
}
// MARK: Textfield Validator
extension BankDepositsWithOTPVC : UITextFieldDelegate {
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
// On inputing value to textfield
if (textField.text?.characters.count < 1 && string.characters.count > 0){
let nextTag = textField.tag + 1;
// get next responder
let nextResponder = textField.superview?.viewWithTag(nextTag);
if (nextResponder == nil){
textField.resignFirstResponder()
// nextResponder = textField.superview?.viewWithTag(1);
}
textField.text = string;
nextResponder?.becomeFirstResponder();
return false;
}else if (textField.text?.characters.count >= 1 && string.characters.count > 0){
// maximum 1 digit
textField.text = "";
let nextTag = textField.tag + 1;
// get next responder
let nextResponder = textField.superview?.viewWithTag(nextTag);
if (nextResponder == nil){
textField.resignFirstResponder()
// nextResponder = textField.superview?.viewWithTag(1);
}
textField.text = string;
nextResponder?.becomeFirstResponder();
return false;
}
else if (textField.text?.characters.count >= 1 && string.characters.count == 0){
// on deleteing value from Textfield
let previousTag = textField.tag - 1;
// get next responder
var previousResponder = textField.superview?.viewWithTag(previousTag);
if (previousResponder == nil){
previousResponder = textField.superview?.viewWithTag(1);
}
textField.text = "";
previousResponder?.becomeFirstResponder();
return false;
}
//return true;
guard let text = textField.text else { return true }
let newLength = text.characters.count + string.characters.count - range.length
return newLength <= limitLength
}
}
Objective-C
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{
if ((textField.text.length < 1) && (string.length > 0))
{
NSInteger nextTag = textField.tag + 1;
UIResponder* nextResponder = [textField.superview viewWithTag:nextTag];
if (! nextResponder){
[textField resignFirstResponder];
}
textField.text = string;
if (nextResponder)
[nextResponder becomeFirstResponder];
return NO;
}else if ((textField.text.length >= 1) && (string.length > 0)){
//FOR MAXIMUM 1 TEXT
NSInteger nextTag = textField.tag + 1;
UIResponder* nextResponder = [textField.superview viewWithTag:nextTag];
if (! nextResponder){
[textField resignFirstResponder];
}
textField.text = string;
if (nextResponder)
[nextResponder becomeFirstResponder];
return NO;
}
else if ((textField.text.length >= 1) && (string.length == 0)){
// on deleteing value from Textfield
NSInteger prevTag = textField.tag - 1;
// Try to find prev responder
UIResponder* prevResponder = [textField.superview viewWithTag:prevTag];
if (! prevResponder){
[textField resignFirstResponder];
}
textField.text = string;
if (prevResponder)
// Found next responder, so set it.
[prevResponder becomeFirstResponder];
return NO;
}
return YES;
}
Use this code if you don't want to work with tag and it works better then above
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// On inputing value to textfield
if ((textField.text?.characters.count)! < 1 && string.characters.count > 0){
if(textField == txtOne)
{
txtTwo.becomeFirstResponder()
}
if(textField == txtTwo)
{
txtThree.becomeFirstResponder()
}
if(textField == txtThree)
{
txtFour.becomeFirstResponder()
}
textField.text = string
return false
}
else if ((textField.text?.characters.count)! >= 1 && string.characters.count == 0){
// on deleting value from Textfield
if(textField == txtTwo)
{
txtOne.becomeFirstResponder()
}
if(textField == txtThree)
{
txtTwo.becomeFirstResponder()
}
if(textField == txtFour)
{
txtThree.becomeFirstResponder()
}
textField.text = ""
return false
}
else if ((textField.text?.characters.count)! >= 1 )
{
textField.text = string
return false
}
return true
}
Swift 4
when you input a number from the pin code into a text field, you should let a number be shown and then the next text field will become the first responder, so change the first responder after the code was in the text view
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let textField = textField as? PinCodeTextField else { return true }
if string == "" {// when the backward clicked, let it go :)
return true
}
// when textfield is not empty, well, next number
if textField.pinCode.count == textField.maxCount {
becomeFirstResponder(after: textField)
return false
}
if string.count > textField.maxCharacterCount {// the max character count should be 1
return false
}
return true
}
// now the text field has been filled with a number
func textFieldCotentDidChange(_ textField: UITextField) {
print("didchange")
guard let textField = textField as? PinCodeTextField else { return }
if textField.pinCode.count == 0 {
becomeFirstResponder(before: textField)
}
// when textfield has been filled, ok! next!
if textField.pinCode.count == textField.maxCharacterCount {
becomeFirstResponder(after: textField)
}
}
for more details and the simple demo, see this link
Just use TextFieldDelegate
method and check the length of the TextField after every changes
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let newString = (textField.text! as NSString).stringByReplacingCharactersInRange(range, withString: string)
if newString.characters.count == 1
{
nextTextField.becomeFirstResponder()
return true
}
else
{
return false
}
}