How to auto fetch OTP, if we use multiple text fields
Asked Answered
F

6

24

I know that if we want to auto fetch the OTP(if we use single textfield) we need to use

otpTextField.textContentType = .oneTimeCode

But, If we use multiple textfield(According to following image)

some thing like this

how should we achieve this ?

Feoffee answered 21/11, 2018 at 13:41 Comment(7)
Please be more precise. Are you stuck at designing the UI or you stuck at coding part ?Irvingirwin
Make a custom class UITextField that will have custom bottom drawing border and you good to go! No need to have 4 textfields.Telfer
@TejaNandamuri, i was stuck at coding part. I want to auto fetch the OTPFeoffee
Try this github.com/Datt1994/DPOTPViewRosetta
Refer this gist.github.com/Catherine-K-George/…Submaxillary
@Submaxillary where is the "view element deriving" from? guard let textfield = view.viewWithTag(tag) as? UITextField else { continue }Bohi
@Bohi Just use the parent view of your text field to access viewWithTag. If your OTP text field is in UIViewController, the view element refers to self.view.Submaxillary
T
11

-> From iOS 12 Apple will allow the support to read One Time Code which you will get in the iPhone device. you can split text into four fields and autofilled and manually enter otp and remove one by one and move each textfield.

1) self.textone maxmimum length 4 and other textfield max length 1

2) Add UITextFieldDelegate

enter image description here

if #available(iOS 12.0, *) {
   txtOne.textContentType = .oneTimeCode
}
self.txtOne.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
self.txtOne.becomeFirstResponder()

  @objc func textFieldDidChange(_ textField: UITextField) {
    if #available(iOS 12.0, *) {
        if textField.textContentType == UITextContentType.oneTimeCode{
            //here split the text to your four text fields
            if let otpCode = textField.text, otpCode.count > 3{
                txtOne.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 0)])
                txtTwo.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 1)])
                txtThree.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 2)])
                txtFour.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 3)])
            }
        }
     } 
  }

 func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {

       if (string.count == 1){
           if textField == txtOne {
               txtTwo?.becomeFirstResponder()
           }
           if textField == txtTwo {
               txtThree?.becomeFirstResponder()
           }
           if textField == txtThree {
               txtFour?.becomeFirstResponder()
           }
           if textField == txtFour {
               txtFour?.resignFirstResponder()
               textField.text? = string
                //APICall Verify OTP
               //Timer.scheduledTimer(timeInterval: 0.1, target: self, selector: #selector(self.VerifyOTPAPI), userInfo: nil, repeats: false)
           }
           textField.text? = string
           return false
       }else{
           if textField == txtOne {
               txtOne?.becomeFirstResponder()
           }
           if textField == txtTwo {
               txtOne?.becomeFirstResponder()
           }
           if textField == txtThree {
               txtTwo?.becomeFirstResponder()
           }
           if textField == txtFour {
               txtThree?.becomeFirstResponder()
           }
           textField.text? = string
           return false
       }

   }
Tedesco answered 6/2, 2020 at 9:26 Comment(4)
textFieldDidChange this method is not getting called, even after setting the delegates. please helpThursby
@ArshadShaik storyboard connect textField delegate or not?Tedesco
@ArshadShaik plz verify textField delegate mentation viewcontroller or storyboard design.Tedesco
delegates are assigned in Viewcontroller code. My OTP is 6 digits, it is display first 4 digits in last 4 text fieldsThursby
S
6

I was stuck with Firebase OneTimeCode in 6 different UITextFields and manage to allow the OS to autofill it from Text Message, also to allow the user to copy and paste it and of course to allow the user to insert it one by one by implementing shouldChangeCharactersIn in a very manual but effective way:

   func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {

        //This lines allows the user to delete the number in the textfield.
        if string.isEmpty{
            return true
        }
        //----------------------------------------------------------------

        //This lines prevents the users from entering any type of text.
        if Int(string) == nil {
            return false
        }
        //----------------------------------------------------------------

        //This lines lets the user copy and paste the One Time Code.
        //For this code to work you need to enable subscript in Strings https://gist.github.com/JCTec/6f6bafba57373f7385619380046822a0
        if string.count == 6 {
            first.text = "\(string[0])"
            second.text = "\(string[1])"
            third.text = "\(string[2])"
            fourth.text = "\(string[3])"
            fifth.text = "\(string[4])"
            sixth.text = "\(string[5])"

            DispatchQueue.main.async {
                self.dismissKeyboard()
                self.validCode()
            }
        }
        //----------------------------------------------------------------

        //This is where the magic happens. The OS will try to insert manually the code number by number, this lines will insert all the numbers one by one in each TextField as it goes In. (The first one will go in normally and the next to follow will be inserted manually)
        if string.count == 1 {
            if (textField.text?.count ?? 0) == 1 && textField.tag == 0{
                if (second.text?.count ?? 0) == 1{
                    if (third.text?.count ?? 0) == 1{
                        if (fourth.text?.count ?? 0) == 1{
                            if (fifth.text?.count ?? 0) == 1{
                                sixth.text = string
                                DispatchQueue.main.async {
                                    self.dismissKeyboard()
                                    self.validCode()
                                }
                                return false
                            }else{
                                fifth.text = string
                                return false
                            }
                        }else{
                            fourth.text = string
                            return false
                        }
                    }else{
                        third.text = string
                        return false
                    }
                }else{
                    second.text = string
                    return false
                }
            }
        }
        //----------------------------------------------------------------


        //This lines of code will ensure you can only insert one number in each UITextField and change the user to next UITextField when function ends.
        guard let textFieldText = textField.text,
            let rangeOfTextToReplace = Range(range, in: textFieldText) else {
                return false
        }
        let substringToReplace = textFieldText[rangeOfTextToReplace]
        let count = textFieldText.count - substringToReplace.count + string.count


        if count == 1{
            if textField.tag == 0{
                DispatchQueue.main.async {
                    self.second.becomeFirstResponder()
                }

            }else if textField.tag == 1{
                DispatchQueue.main.async {
                    self.third.becomeFirstResponder()
                }

            }else if textField.tag == 2{
                DispatchQueue.main.async {
                    self.fourth.becomeFirstResponder()
                }

            }else if textField.tag == 3{
                DispatchQueue.main.async {
                    self.fifth.becomeFirstResponder()
                }

            }else if textField.tag == 4{
                DispatchQueue.main.async {
                    self.sixth.becomeFirstResponder()
                }

            }else {
                DispatchQueue.main.async {
                    self.dismissKeyboard()
                    self.validCode()
                }
            }
        }

        return count <= 1
        //----------------------------------------------------------------

    }

Note: I use a subscript string method in this code, you can get this extension here, String+Subscript.swift

And of course don't forget to assign the delegate and the .oneTimeCode to the TextField.

textField.delegate = self
textField.textContentType = .oneTimeCode
Severe answered 11/12, 2019 at 15:52 Comment(2)
Perfect Solution. Thank you!!@Juan CarlosCorinacorine
@Feoffee What do you think, is it the answer you where looking for? if it is please mark it as correct. :)Severe
C
4

If you can get the auto OTP for single field, you can split that text into your four text fields. I believe.

You may have to use textField's change observer as like below,

textField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
func textFieldDidChange(_ textField: UITextField) {

        // here check you text field's input Type
        if textField.textContentType == UITextContentType.oneTimeCode{

            //here split the text to your four text fields

            if let otpCode = textField.text, otpCode.count > 3{

                textField.text = String(otpCode[otpCode.startIndex])
                textField1.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 1)])
                textField2.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 2)])
                textField3.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 3)])
        }
    }

}
Crean answered 21/11, 2018 at 14:31 Comment(2)
This is not working. As textField.text is giving empty text when I click on OTP suggestion on keyboard.Allemande
In this case If user enter manually OTP it will not go to next field, In else part if we assign becomeFirstResponder to next the textfield to handle manually OTP insertion and if focus textfield is not 1st one then autoFilled not working can you tell me solution for both autofilled and if user enter manually then cursor will move to next field automatically in same code?Keg
M
3

What I do is similar to @Natarajan's answer, but I use UITextFieldDelegate method. On viewDidAppear your first text field should become first responder and be of type oneTimeCode.

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {        
    // Fill your textfields here

    return true
}
Malacostracan answered 31/7, 2019 at 12:1 Comment(0)
O
1
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    if string.length > 1 {
        textFieldDidChange(textField, otpCode: string)
        return false
    }
}

func textFieldDidChange(_ textField: UITextField, otpCode: String) {
      if textField.textContentType == UITextContentType.oneTimeCode{
          //here split the text to your four text fields
          if otpCode.count == 4, Int(otpCode) != nil {
              otp_field_1.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 0)])
              otp_field_2.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 1)])
              otp_field_3.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 2)])
              otp_field_4.text = String(otpCode[otpCode.index(otpCode.startIndex, offsetBy: 3)])
              
              let textFields = [otp_field_1, otp_field_2, otp_field_3, otp_field_4]
              for i in 0..<textFields.count{
                  textFields[i].layer.borderColor = UIColor.GREEN_COLOR.cgColor
              }
          } else {
              textField.text = ""
          }
          textField.resignFirstResponder()
      }
}
Olpe answered 11/11, 2022 at 11:54 Comment(0)
B
0

Here's how I've done it with multiple textfields, paste and remove options

@IBOutlet weak var otpTextField1: UITextField!
@IBOutlet weak var otpTextField2: UITextField!
@IBOutlet weak var otpTextField3: UITextField!
@IBOutlet weak var otpTextField4: UITextField!
@IBOutlet weak var nextButton: UIButton!

Properties

private let maxLengthPhoneNumber = 1
private let acceptableNumbers = "0123456789"
private var OTP = "OTP"

In viewDidLoad()

override func viewDidLoad() {
    super.viewDidLoad()

    setupView()
}


private func setupView() {
    
    otpTextField1.delegate = self
    otpTextField1.layer.borderWidth = 1
    otpTextField1.layer.cornerRadius = 8
    otpTextField1.textContentType = .oneTimeCode
    otpTextField1.becomeFirstResponder()
    
    otpTextField2.delegate = self
    otpTextField2.layer.borderWidth = 1
    otpTextField2.layer.cornerRadius = 8
    otpTextField2.textContentType = .oneTimeCode
    
    
    otpTextField3.delegate = self
    otpTextField3.layer.borderWidth = 1
    otpTextField3.layer.cornerRadius = 8
    otpTextField3.textContentType = .oneTimeCode
    
    
    otpTextField4.delegate = self
    otpTextField4.layer.borderWidth = 1
    otpTextField4.layer.cornerRadius = 8
    otpTextField4.textContentType = .oneTimeCode
    
    
    nextButton.isUserInteractionEnabled = false
    
}

Paste

private func otpPaste(_ textField: UITextField, _ string: String) {
    if textField.textContentType == UITextContentType.oneTimeCode {
        otpTextField1.becomeFirstResponder()
        //split the text to four text fields
        otpTextField1.text = String(string[string.index(string.startIndex, offsetBy: 0)])
        otpTextField2.text = String(string[string.index(string.startIndex, offsetBy: 1)])
        otpTextField3.text = String(string[string.index(string.startIndex, offsetBy: 2)])
        otpTextField4.text = String(string[string.index(string.startIndex, offsetBy: 3)])
        otpTextField1.resignFirstResponder()
    }
}

OTP Send to API

private func otpSentToApi(_ textField: UITextField) {
    
    if otpTextField1.text != "" && otpTextField2.text != "" && otpTextField3.text != "" && otpTextField4.text != "" {
        print( "not '' check")
        textField.resignFirstResponder()//
        
        //api call
        OTP = "\(otpTextField1.text!)\(otpTextField2.text!)\(otpTextField3.text!)\(otpTextField4.text!)"
        print("OTP  \(OTP)")
        
        //api success
        //unlocked
        nextButton.isUserInteractionEnabled = true

    }
}

In the Textfield Delegates

extension OTPViewController: UITextFieldDelegate {

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    
    let numberOnly = NSCharacterSet(charactersIn: acceptableNumbers).inverted
    let strValid = string.rangeOfCharacter(from: numberOnly) == nil
    
    // number validation
    if (strValid) {
        
        // tf paste input
        if string.count == 4 {
            
            otpPaste(textField, string) //paste otp
            otpSentToApi(textField) //check for not empty & send api

            return false
        }
        
        // tf single input
        else if string.count == 1 {
            
            if textField == otpTextField1 {
                otpTextField2.becomeFirstResponder()
                textField.text? = string
                
                otpSentToApi(textField) //check for not empty & send api
                
            }
            if textField == otpTextField2 {
                if otpTextField1.text != "" {
                    
                    otpTextField3.becomeFirstResponder()
                    textField.text? = string
                }
                else {
                    otpTextField1.becomeFirstResponder()
                    otpTextField1.text = string
                    textField.becomeFirstResponder()
                }
                
                otpSentToApi(textField) //check for not empty & send api

            }
            if textField == otpTextField3 {
                if otpTextField2.text != "" {
                    
                    otpTextField4.becomeFirstResponder()
                    textField.text? = string
                }
                else {
                    otpTextField1.becomeFirstResponder()
                    otpTextField1.text = string
                    otpTextField2.becomeFirstResponder()
                }
                
                otpSentToApi(textField) //check for not empty & send api
                
            }
            if textField == otpTextField4 {
                if otpTextField2.text != "" {
                    
                    otpTextField4.resignFirstResponder()
                    textField.text? = string
                }
                else {
                    otpTextField1.becomeFirstResponder()
                    otpTextField1.text = string
                    otpTextField2.becomeFirstResponder()
                }
                
                otpSentToApi(textField) //check for not empty & send api

            }
            return false
        }
        else if string.count == 0 {
            
            if textField == otpTextField4 {
                
                otpTextField3?.becomeFirstResponder()
                textField.text? = string
            }
            
            if textField == otpTextField3 {
                
                if otpTextField2.text != "" && otpTextField4.text != "" {
                    textField.becomeFirstResponder()
                    textField.text = string
                }

                else {
                    otpTextField2?.becomeFirstResponder()
                    textField.text? = string
                }
            }
            
            if textField == otpTextField2 {
                
                if otpTextField1.text != "" && otpTextField3.text != "" {
                    textField.becomeFirstResponder()
                    textField.text = string
                }

                else {
                    otpTextField1?.becomeFirstResponder()
                    textField.text? = string
                }
                
            }
            
            if textField == otpTextField1 {
                otpTextField1?.becomeFirstResponder()
                textField.text? = string
            }
            // locked
            nextButton.isUserInteractionEnabled = false

            return false
        }
        
        else {
            return false
        }
        
    }
    else {
        return strValid
    }
    
}


//textfield func for the touch on BG
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
    self.view.endEditing(true)
}

}

Boucicault answered 14/6, 2023 at 5:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.