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()
}
}
Related
I want to deactivate the code below when the delete button is clicked.
I want to show the value entered in textfield as (XXX) XXX XXXX but when delete button is clicked, it is not deleted due to the insert.
How can I do this?
my code:
extension CreateOrEditVC:UITextFieldDelegate{
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if number.text!.count == 1 {
number.text!.insert("(", at: number.text!.startIndex)
}
if number.text!.count == 4 {
number.text!.insert(")", at: number.text!.index(number.text!.startIndex, offsetBy: 4))
}
if number.text!.count == 5 {
number.text!.insert(" ", at: number.text!.index(number.text!.startIndex, offsetBy: 5))
}
if number.text!.count == 9 {
number.text!.insert(" ", at: number.text!.index(number.text!.startIndex, offsetBy: 9))
}
guard let textFieldText = number.text, let rangeOfTextToReplace = Range(range, in: textFieldText) else {
return false
}
let substringToReplace = textFieldText[rangeOfTextToReplace]
let count = textFieldText.count - substringToReplace.count + string.count
return count <= 14
}
}
//there is a param 'replacementString string'
if string == "" {
//delete
return true
}
I wrote this code for 4 textfields accepting each of the four characters for OTP. However I also want the user to avoid going to a random textfield themselves:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if(((textField.text?.count)! < 1) && (string.count > 0))
{
if textField == charForOTP1
{
charForOTP2.becomeFirstResponder()
}
else if textField == charForOTP2
{
charForOTP3.becomeFirstResponder()
}
else if textField == charForOTP3
{
charForOTP4.becomeFirstResponder()
}
else if textField == charForOTP4
{
charForOTP4.resignFirstResponder()
}
textField.text = string
return false
}
else if( (textField.text?.count)! >= 1) && (string.count == 0)
{
if textField == charForOTP2
{
charForOTP1.becomeFirstResponder()
}
else if textField == charForOTP3
{
charForOTP2.becomeFirstResponder()
}
else if textField == charForOTP4
{
charForOTP3.becomeFirstResponder()
}
else if textField == charForOTP1
{
charForOTP1.becomeFirstResponder()
}
textField.text = ""
return false
}
else if (textField.text?.count)! >= 1
{
textField.text = string
return false
}
return true;
}
I have tried implementing the same. However it resulted in some unexpected results.
What exactly should I add here for the same to work?
I hope your requirement is as:
First, you need only one textfield active, as soon as user inputted value next respective textfield will become active when reached to the last textfield you want to enable all textfields.
We need to add notification when textfield value changed in viewDidLoad method. (I have taken otp1, otp2, otp3, opt4 textfields also assign tags from 1 to 4 to all textfield).
otp1.addTarget(self, action: #selector(textFieldDidChange(textField:)), for: .editingChanged)
otp2.addTarget(self, action: #selector(textFieldDidChange(textField:)), for: .editingChanged)
otp3.addTarget(self, action: #selector(textFieldDidChange(textField:)), for: .editingChanged)
otp4.addTarget(self, action: #selector(textFieldDidChange(textField:)), for: .editingChanged)
I have written updateTextFields(textfield: UITextField) function which will updates whatever actions need to give to active/inactive textfield. To active first textfield we need to call self.updateTextFields(textfield: otp1) from viewDidLoad.
/// Updates actions for current textfield
///
/// - Parameter textfield: 'UITextField' object
func updateTextFields(textfield: UITextField) {
var enableArray = [UITextField]()
var disableArray = [UITextField]()
var sTextField: UITextField = UITextField()
switch textfield.tag {
case 1:
sTextField = otp1
enableArray.append(otp1)
disableArray.append(otp2)
disableArray.append(otp3)
disableArray.append(otp4)
break
case 2:
sTextField = otp2
enableArray.append(otp2)
disableArray.append(otp1)
disableArray.append(otp3)
disableArray.append(otp4)
break
case 3:
sTextField = otp3
enableArray.append(otp3)
disableArray.append(otp2)
disableArray.append(otp1)
disableArray.append(otp4)
break
case 4:
sTextField = otp4
enableArray.append(otp4)
enableArray.append(otp1)
enableArray.append(otp2)
enableArray.append(otp3)
break
}
for ltextfield in disableArray {
ltextfield.isEnabled = false
}
for ltextfield in enableArray {
ltextfield.isEnabled = true
}
sTextField.becomeFirstResponder()
}
Now the actual UITextFieldDelegate method to check the inputted character.
// MARK: UITextField Delegate Method
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if string != "" {
var text = textField.text ?? ""
text.append(string)
if text.count > 1 {
return false
}
let allowedCharacters = CharacterSet(charactersIn:"0123456789 ")//Here change this characters based on your requirement
let characterSet = CharacterSet(charactersIn: string)
return allowedCharacters.isSuperset(of: characterSet)
}
return true
}
At last, our notification method which we have added in viewDidLoad method. Here we have checked textfield count and based on that we have move curser.
#objc private func textFieldDidChange(textField: UITextField) {
if textField.text?.count == 1, (textField.tag + 1) < 5 {
self.updateTextFields(textfield: self.getTextField(isForward: true, currentTextField: textField))
} else {
if textField.text?.count == 0, (textField.tag - 1) > 0 {
self.updateTextFields(textfield: self.getTextField(isForward: false, currentTextField: textField))
}
}
}
Hope this will helps you.
EDIT
Added Get Text field Method
/// Get TextField Object For Responder which is either active or resign
///
/// - Parameters:
/// - isForward: flag used to find whether we want next or previous text field
/// - currentTextField: TextField which is active
/// - Returns: TextField if it satisfied condition
func getTextField(isForward: Bool, currentTextField: UITextField) -> UITextField {
if isForward {
if (currentTextField.tag + 1) < 5 {
switch currentTextField.tag {
case 1:
return otp2
case 2:
return otp3
case 3:
return otp4
default:
break
}
}
} else {
if (currentTextField.tag - 1) > 0 {
switch currentTextField.tag {
case 4:
return otp3
case 2:
return otp1
case 3:
return otp2
default:
break
}
}
}
return UITextField()
}
I am trying to have it so that when a user presses the "backspace/delete" button when they're inputting a PIN number, it goes to and deletes the "previous" textfield. I saw other solutions on SO and the function keyboardInputShouldDelete seemed promising but it didn't work. Any help is greatly appreciated. Below is my code:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if ((textField.text?.count)! < 1) && (string.count > 0) {
if textField == txtOTP1 {
txtOTP2.becomeFirstResponder()
}
if textField == txtOTP2 {
txtOTP3.becomeFirstResponder()
}
if textField == txtOTP3 {
txtOTP4.becomeFirstResponder()
}
if textField == txtOTP4 {
check()
}
textField.text = string
userPIN = "\(self.txtOTP1.text!)\(self.txtOTP2.text!)\(self.txtOTP3.text!)\(self.txtOTP4.text!)"
return false
} else if ((textField.text?.count)! >= 1) && (string.count == 0) {
if textField == txtOTP4 {
txtOTP3.becomeFirstResponder()
}
if textField == txtOTP3 {
txtOTP2.becomeFirstResponder()
}
if textField == txtOTP2 {
txtOTP1.becomeFirstResponder()
}
if textField == txtOTP1 {
}
textField.text = ""
return false
}
else if (textField.text?.count)! >= 1 {
textField.text = string
return false
}
return true
}
func keyboardInputShouldDelete(_ textField: UITextField) -> Bool {
textField.delegate = self
return true
}
Try do it like this:
//Get new length of the text after input
let newLength = (textField.text ?? "").count + string.count - range.length
if newLength == 0 {
//Go back prev textfield if new length is 0
}
To overcome the issue where the shouldChangeCharactersIn won't get called on empty field, simply carry the last character user input from last textfield to the next, so user will never have an empty field aside from the first textfield.
Another way is add a space character by default for each textfield and detect around it but it seems to be more annoying to dealt with.
I have 4 UItextfield for enter the top number. Like 1 2 3 4. When I enter the top number in my all text field, and if I am in last urtext field - and if I press backward button or x key in my key board - My number are in each text field are not getting deleted. I was not able to delete.
Here is my code :
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let value = textField.text!
let length = value.characters.count
if (length == 1) {
return false
}
if (textField == Field1) {
self.performSelector(Selector("setNextResponder:"), withObject: Field2, afterDelay: 0.2)
textField1 = string
} else if (textField == Field2) {
self.performSelector(Selector("setNextResponder:"), withObject: Field3, afterDelay: 0.2)
textField2 = string
} else if (textField == Field3) {
self.performSelector(Selector("setNextResponder:"), withObject: Field4, afterDelay: 0.2)
textField3 = string
} else if (textField == Field4) {
textField.resignFirstResponder()
textField4 = string
nextButton.enabled = true
}
else if (textField == Field4)
{
if string == "" && textField.text?.characters.count == 1 {
Field1.text = ""
Field2.text = ""
Field3.text = ""
Field4.text = ""
Field1.becomeFirstResponder()
}
}
return true
}
What i am missing?
You can make it like this:
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
if textField.text?.characters.count == 1 && string == "" {
if textField == txt2 {
txt2.text = ""
txt1.becomeFirstResponder()
} else if textField == txt3 {
txt3.text = ""
txt2.becomeFirstResponder()
} else if textField == txt4 {
txt4.text = ""
txt3.becomeFirstResponder()
}
}
return true
}
Still if possible you should create a separate button with clearText action to clear all textfield instead of clearing the textfield from the clear button. It will look as better UI/UX as per the default iOS clear button behaviour
Try :
func textFieldShouldClear(textField: UITextField) -> Bool {
textField.text = ""
textField.resignFirstResponder()
return false
}
Also choose an appropriate option from attribute inspector.
This code works perfectly, and I can't key in anything other than integers, even when I try to paste it in.
I'd like to add one more refinement, which is to limit the length of the input. Here's my code:
func initializeTextFields()
{
APTeams.delegate = self
APTeams.keyboardType = UIKeyboardType.NumberPad
APRounds.delegate = self
APRounds.keyboardType = UIKeyboardType.NumberPad
APBreakers.delegate = self
APBreakers.keyboardType = UIKeyboardType.NumberPad
}
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
// Find out what the text field will be after adding the current edit
let text = (textField.text! as NSString).stringByReplacingCharactersInRange(range, withString: string)
if text == "" {
return true
}
if let _ = Int(text) {
return true
}
else {
return false
}
}
What do I have to add to it to achieve this? The maximum input length for all the TextFields should be <= 4.
BTW, all code is in Swift 2. From problems I faced when trying to implement answers to questions I've asked before, I gather that some of the methods are different.
count(textField.text) is deprecated in SWIFT 2.0
public func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
if let textField = textField as? UITextField {
if (range.length + range.location > textField.text!.characters.count) {
return false;
}
let newLength = textField.text!.characters.count + string.characters.count - range.length;
switch(textField.tag) { //In case you want to handle multiple textfields
case Constants.TAG1:
return newLength <= 20;
case Constants.TAG2:
return newLength <= 30;
default:
return newLength <= 15;
}
}
return true;
}
Write the condition in textfield delegate method as:-
func textField(textField: UITextField!, shouldChangeCharactersInRange range: NSRange, replacementString string: String!) -> Bool {
if (count(textField.text) > 4 && range.length == 0)
{
return false // return NO to not change text
}
else
{
}
write all your code part in else part.
The delegate methods or an NSFormatter such as NSNumberFormatter.
The formatter is the most appropriate generally as it also provides localization support.
I know its bit too late but still I want share it too, I found a way which is much easier to set a limit character for an textfield in swift development.
Here is the code:-
import UIKit
private var maxLengths = [UITextField: Int]()
extension UITextField {
#IBInspectable var maxLength: Int {
get {
guard let length = maxLengths[self] else {
return Int.max
}
return length
}
set {
maxLengths[self] = newValue
addTarget(self, action: #selector(limitLength), for: .editingChanged)
}
}
#objc func limitLength(textField: UITextField) {
guard let prospectiveText = textField.text, prospectiveText.count > maxLength else {
return
}
let selection = selectedTextRange
let maxCharIndex = prospectiveText.index(prospectiveText.startIndex, offsetBy: maxLength)
#if swift(>=4.0)
text = String(prospectiveText[..<maxCharIndex])
#else
text = prospectiveText.substring(to: maxCharIndex)
#endif
selectedTextRange = selection
}
}
and just set the limit through the panel.
Image:
Just try this to limit the length of TF
Editing changed Action Outlet of TF
#IBAction func otpTF2EditingChnaged(_ sender: UITextField) {
if (sender.text?.count == 1) {
otpTF3.becomeFirstResponder()
}
checkMaxLength(textField: sender , maxLength: 1)
}
Function That will limit the length
private func checkMaxLength(textField: UITextField!, maxLength: Int) {
if (textField.text!.count > maxLength) {
textField.deleteBackward()
}
}