如果我们使用多个文本字段,如何自动获取OTP?

24

我知道如果我们想要自动获取OTP(如果我们使用单个文本字段),我们需要使用

otpTextField.textContentType = .oneTimeCode

但是,如果我们使用多个文本框(如下图所示),

类似于这样的某物

我们应该如何实现?


请更加具体些。您是在设计用户界面方面卡住了还是在编码方面卡住了? - Teja Nandamuri
2
创建一个自定义类 UITextField,它将具有自定义的底部绘制边框,然后您就可以开始使用了!不需要拥有4个文本字段。 - inokey
1
请参考此链接 https://gist.github.com/Catherine-K-George/c91e72eb46a260d5045eded3b47f38fd - Catherine
@Catherine,“view element deriving”是从哪里来的?guard let textfield = view.viewWithTag(tag) as? UITextField else { continue } - Geob
@Geob 只需使用您的文本字段的父视图来访问 viewWithTag。如果您的 OTP 文本字段在 UIViewController 中,则 view 元素指代 self.view。 - Catherine
显示剩余2条评论
6个回答

11

-> 从iOS 12开始,苹果将允许支持读取iPhone设备中的一次性代码。您可以将文本拆分为四个字段,并自动填充和手动输入otp并逐个删除并移动每个文本字段。

1)self.textone最大长度为4,其他文本字段最大长度为1

2)添加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
       }

   }

textFieldDidChange这个方法没有被调用,即使已经设置了代理。请帮忙解决。 - Arshad Shaik
@ArshadShaik storyboard是否连接textField代理? - Himanshu Patel
@ArshadShaik 请验证textField代理实现是在视图控制器或故事板设计中。 - Himanshu Patel
1
委托在Viewcontroller代码中分配。我的OTP是6位数字,它会在最后4个文本字段中显示前4个数字。 - Arshad Shaik

6

我在6个不同的UITextFields中使用Firebase OneTimeCode遇到了问题,设法让操作系统从短信中自动填充它,还允许用户复制和粘贴它,当然也可以一次插入一个字符,通过实现shouldChangeCharactersIn方法以非常手动但有效的方式来实现:

   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
        //----------------------------------------------------------------

    }

注意:我在这段代码中使用了下标字符串方法,你可以在这里获取此扩展,String+Subscript.swift
当然别忘了将委托和.oneTimeCode分配给TextField。
textField.delegate = self
textField.textContentType = .oneTimeCode

1
完美的解决方案。谢谢!@Juan Carlos - Vinayak Bhor
@Bhanuteja 你觉得这是你在寻找的答案吗?如果是,请将其标记为正确。 :) - Juan Carlos

4

如果您可以获取单个字段的自动OTP,那么您可以将该文本拆分成四个文本字段。我相信。

您可能需要使用textField的更改观察器,如下所示:

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)])
        }
    }

}

5
无法正常工作。当我点击键盘上的 OTP 建议时,textField.text 返回空文本。 - nitin.agam
在这种情况下,如果用户手动输入OTP,则不会进入下一个字段。否则,如果我们将becomeFirstResponder分配给下一个文本字段以处理手动OTP插入,并且焦点文本字段不是第一个,则自动填充无法工作。你能告诉我如何解决自动填充和如果用户手动输入,则光标将自动移动到下一个字段的问题吗? - alphanso

3
我所做的与@Natarajan的回答类似,但我使用UITextFieldDelegate方法。 在viewDidAppear上,你的第一个文本字段应该成为第一响应者,并且其类型应为oneTimeCode
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {        
    // Fill your textfields here

    return true
}

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()
      }
}

0

这是我如何使用多个文本字段、粘贴和删除选项完成的

@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!

属性

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

在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
    
}

粘贴

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发送到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

    }
}

在文本框代表中

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)
}

}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接