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)
how should we achieve this ?
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)
how should we achieve this ?
-> 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
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
}
}
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
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)])
}
}
}
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
}
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()
}
}
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)
}
}
© 2022 - 2024 — McMap. All rights reserved.
UITextField
that will have custom bottom drawing border and you good to go! No need to have 4 textfields. – Telfer