0

I have four UITextFields, each of which represents a single digit of an OTP. I want the control to shift to consecutive textfield as the user types in the code. My implementation is below.

extension FillSignUpCodeController: UITextFieldDelegate {

    func textFieldDidBeginEditing(_ textField: UITextField) {
        textField.text = ""
    }

    func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
        let inputString = (textField.text! as NSString).replacingCharacters(in: range, with: string)

        if inputString.count == 1 {
            switch textField {

            case textFieldCodeFirstDigit:
                textFieldCodeFirstDigit.text = inputString
                textFieldCodeFirstDigit.resignFirstResponder()
                textFieldCodeSecondDigit.becomeFirstResponder()

            case textFieldCodeSecondDigit:
                textFieldCodeSecondDigit.text = inputString
                textFieldCodeSecondDigit.resignFirstResponder()
                textFieldCodeThirdDigit.becomeFirstResponder()

            case textFieldCodeThirdDigit:
                textFieldCodeThirdDigit.text = inputString
                textFieldCodeThirdDigit.resignFirstResponder()
                textFieldCodeFourthDigit.becomeFirstResponder()

            case textFieldCodeFourthDigit:
                textFieldCodeFourthDigit.text = inputString
                textFieldCodeFourthDigit.resignFirstResponder()

            default:
                return false
            }
        }
        return true
    }
}

With this piece of code, as the user types the first digit, the first textfield takes the input value and moves the control to the next textfield. However, the second text field is taking the value of the first digit. I tried setting the text to empty after changing the firstResponder but it did not work. How can I fix this issue? Thanks.

Sujal
  • 1,447
  • 19
  • 34

3 Answers3

1

Since textField(_:shouldChangeCharactersIn:replacementString:) executes before the text is present in the text field, you should put the code inside a method that is called when the text did already change.

You should observe the changes of your text field and execute the responder-changing code there. You can find more information about that in this question.

Moreover, resigning the first responder before changing it is redundant, you don't need to do that.

It is also very redundant to handle every text field separately. I'd recommend including your text fields in an array and iterating through them.

Tamás Sengel
  • 55,884
  • 29
  • 169
  • 223
1

shouldChangeCharactersInRange gets called before the textField is filled. You can do it by:

    textField.addTarget(self, action: #selector(textFieldChangedValue(textField:)), for: UIControlEvents.editingChanged)

write above line for all textfields in viewDidLoad()

    @objc func textFieldChangedValue(textField: UITextField) {
    print(textField.text)
}

This will work

Rahul Dasgupta
  • 894
  • 6
  • 18
1

Following the answers from @the4kman and @Rahul Dasgupta, I have implemented the following:

FillUpCodeViewController.swift

override func viewDidLoad() {

    setupArrrayofTextFields(textField1: textFieldCodeFirstDigit,
                            textField2: textFieldCodeSecondDigit,
                            textField3: textFieldCodeThirdDigit,
                            textField4: textFieldCodeFourthDigit)
}

func setupArrrayofTextFields(textField1: UITextField, textField2: UITextField,
                             textField3: UITextField, textField4: UITextField) {

    arrayOftextFields = [textField1, textField2, textField3, textField4]
    for textField in arrayOftextFields {
        textField.addTarget(self, action: #selector(textFieldDidChange(textField:)), for: .editingChanged)
    }
}

@objc func textFieldDidChange(textField: UITextField) {
    guard textField.text?.count == 0 else {
        let index: Int = arrayOftextFields.index(of: textField)!
        guard index == (arrayOftextFields.count-1) else {
            arrayOftextFields[index+1].becomeFirstResponder()
            return
        }
        textField.resignFirstResponder()
        return
    }
}

And, again in the viewcontroller in which I have to implement the submission of recovery code, I simply inherited the FillUpCodeViewController class.

RecoveryViewController.swift

override func viewDidLoad() {

    setupArrrayofTextFields(textField1: textFieldRecoveyCodeFirstDigit,
                            textField2: textFieldRecoveyCodeSecondDigit,
                            textField3: textFieldRecoveyCodeThirdDigit,
                            textField4: textFieldRecoveyCodeFourthDigit)
}
Sujal
  • 1,447
  • 19
  • 34