Swift UITextField deleteBackward Issue - ios

I have a series of textfields in a single row. Each textfield is limited to two characters.
My problem is that I want to delete all the characters and go back through all the textfields until I get to the first textfield.
I have subclassed the textfield and overridden the deleteBackward method to determine if the text in the textfield is empty. If so then change the responder to the previous textfield.
This is all fine, but when there is one character in the textfield and backspace is pressed, the character is deleted and the responder is changed to the previous textfield.
This is incorrect behaviour as I want the focus to stay on that textfield that has had its character deleted and if the delete key is pressed again, then responder should change to previous textfield.
I corrected this my setting a flag in the shouldChangeCharacters delegate method of the textfield. The flag is set to true if ...
if (newLength == 0 && currentLength == 0) || currentLength == 0 {
textFieldEmpty = true
} else {
textFieldEmpty = false
}
This works fine, but when the textfield is empty, and backspace is pressed, this delegate method is not called?
Just wandering if anyone has solved a similar problem and what approach they may have taken?
Thanks

I have a similar scenario but with single characters in every text field. I have also used deleteBackward in a custom textField to achieve this.
Try this code:
import UIKit
protocol CustomTextFieldDelegate{
func textFieldDidDelete()
}
class CustomTextField: UITextField {
var myCustomTextFieldDelegate : CustomTextFieldDelegate!
override func deleteBackward() {
super.deleteBackward()
myCustomTextFieldDelegate.textFieldDidDelete()
}
}
And this is how you should implement the deleteBackward method:
func textFieldDidDelete() {
print("Entered Delete");
print("Tag!!\(currentActiveTextField.tag)")
var previousTag = 0
if currentActiveTextField.text == ""{
previousTag = currentActiveTextField.tag - 1
}else{
previousTag = currentActiveTextField.tag
}
let previousResponder = currentActiveTextField.superview?.viewWithTag(previousTag)
if(previousTag > 0){
let previousTextField = previousResponder as! CustomTextField
previousTextField.text = ""
currentActiveTextField = previousTextField
previousResponder?.becomeFirstResponder()
}
}
Also in shouldChangeCharacters in range method, dont forget to set your currentlyActiveTextField as the text field you wish to edit. Check the following code, might help. My text fields have tags 1,2,3,4.
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
currentActiveTextField = textField as! CustomTextField
let nextTag = textField.tag + 1;
// get next responder
var nextResponder = textField.superview?.viewWithTag(nextTag);
// On inputing value to textfield
if (textField.text?.characters.count < 1 && string.characters.count > 0){
if (nextResponder == nil){
nextResponder = textField.superview?.viewWithTag(1);
}
textField.text = string;
if nextTag == 5 {
self.digitFourTextField.resignFirstResponder()
return true;
}
nextResponder?.becomeFirstResponder();
return false;
}
if (textField.text?.characters.count >= 1 && string.characters.count == 0){
// on deleteing value from Textfield
let presentTag = textField.tag;
// get next responder
var presentResponder = textField.superview?.viewWithTag(presentTag);
if (presentResponder == nil){
presentResponder = textField.superview?.viewWithTag(1);
}
textField.text = "";
presentResponder?.becomeFirstResponder();
return false;
}
let currentCharacterCount = textField.text?.characters.count ?? 0
let newLength = currentCharacterCount + string.characters.count - range.length
if newLength > 1 {
textField.text = string
if (nextResponder != nil){
nextResponder?.becomeFirstResponder();
}
}
let returnVal = newLength <= 1
return returnVal;
}

Call the delegate method and then call super.deleteBackwards() after.
This way, at the time you call the delegate method the text.count == 1. If you flip the order and call super.deleteBackwards() first the text.count == 0 and you cannot accurately check for an empty string.

Related

Pressing Backspace/Delete in TextField iOS

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.

Which event is used to detect the UITextfield in null state

i'm trying develop a textfield switching from 1 to another automatically, similarly i can switch the textfield backwards, but in null state i can't switch the cursor into the previous textfield , i tried with EditingChange Event but when the textfield in null state the Editng Change function is not fired.
The code is :
#IBAction func textFieldEditingChanged(_ sender: UITextField) {
if(sender.text?.characters.count == 1) {
switch sender {
case otpField1:
otpField2.becomeFirstResponder()
case otpField2:
otpField3.becomeFirstResponder()
case otpField3:
otpField4.becomeFirstResponder()
case otpField4:
otpField4.resignFirstResponder()
default:
break
}
}else {
}
if ((sender.text?.characters.count)! <= 0 || sender.text! == "" || sender.text?.isEmpty == true) {
switch sender {
case otpField4:
otpField3.becomeFirstResponder()
case otpField3:
otpField2.becomeFirstResponder()
case otpField2:
otpField1.becomeFirstResponder()
case otpField1:
otpField1.resignFirstResponder()
default:
break
}
}
}
In order find UITextField empty you can use isEmpty of String.
So here it will be:
sender.text.isEmpty
How I will use is
guard let email = emailTextField.text, !email.isEmpty else {
return
}
Hopes this solves your problem.
Rather implement the textfield delegate shouldChangeCharactersInRange and check in there, probably need to check for nil and some more stuff but this is the idea:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let newLength = (textField.text?.characters.count)! - range.length + string.characters.count
if (textField.text?.characters.count)! > 0 && newLength == 0 {
textField.text = ""
switch textField {
....
}
}
return true
}
You can find with condition like
if textField.isEmpty or textField.text == "" {
// Do your stuff here when textField is empty
}
else {
// Do your stuff when textField is not empty
}

Set UITextField to mm-dd-yy format in swift

So I want to have a UITextField to only accept digits, solved that by using a custom keyboard input.
The intention of this UITextField is to get someones birthday. I don't want to use an UIDatePicker tough as I don't like it's appearance.
I'd like that the TextField automatically inserts dashes after every second digit that the user put into the TextField.
dd-mm-yy is the placeholder text. I either thought of making the dashes permanently but I don't know how to do that either.
How can I do this?
You want to allow user to enter text in textfield in this dd-mm-yy right ?
if it so i'am sure this will help you.
In top of your class declare this variable which we gonna use later.
var dateFormate = Bool()
Add delegate and tag for that textfield.
Then add this following delegate method
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
//1. To make sure that this is applicable to only particular textfield add tag.
if textField.tag == 1 {
//2. this one helps to make sure that user enters only numeric characters and '-' in fields
let numbersOnly = NSCharacterSet(charactersInString: "1234567890-")
let characterSetFromTextField = NSCharacterSet(charactersInString: string)
let Validate:Bool = numbersOnly .isSupersetOfSet(characterSetFromTextField)
if !Validate {
return false;
}
if range.length + range.location > textField.text?.characters.count {
return false
}
let newLength = (textField.text?.characters.count)! + string.characters.count - range.length
if newLength == 3 || newLength == 6 {
let char = string.cStringUsingEncoding(NSUTF8StringEncoding)!
let isBackSpace = strcmp(char, "\\b")
if (isBackSpace == -92) {
dateFormate = false;
}else{
dateFormate = true;
}
if dateFormate {
let textContent:String!
textContent = textField.text
//3.Here we add '-' on overself.
let textWithHifen:NSString = "\(textContent)-"
textField.text = textWithHifen as String
dateFormate = false
}
}
//4. this one helps to make sure only 8 character is added in textfield .(ie: dd-mm-yy)
return newLength <= 8;
}
return true
}
That's it now user can enter their DOB.No need to worry about '-' it will be added automatically.
Swift 3:
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
//1. To make sure that this is applicable to only particular textfield add tag.
if textField.tag == 1 {
//2. this one helps to make sure that user enters only numeric characters and '-' in fields
let numbersOnly = CharacterSet(charactersIn: "1234567890-")
let Validate = string.rangeOfCharacter(from: numbersOnly.inverted) == nil ? true : false
if !Validate {
return false;
}
if range.length + range.location > textField.text?.characters.count {
return false
}
let newLength = (textField.text?.characters.count)! + string.characters.count - range.length
if newLength == 3 || newLength == 6 {
let char = string.cString(using: NSUTF8StringEncoding)!
let isBackSpace = strcmp(char, "\\b")
if (isBackSpace == -92) {
dateFormate = false;
}else{
dateFormate = true;
}
if dateFormate {
let textContent:String!
textContent = textField.text
//3.Here we add '-' on overself.
let textWithHifen:NSString = "\(textContent)-"
textField.text = textWithHifen as String
dateFormate = false
}
}
//4. this one helps to make sure only 8 character is added in textfield .(ie: dd-mm-yy)
return newLength <= 8;
}
return true
}
Swift5
// Use textfield delegate shouldChangeCharactersIn
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if (textField.text?.count == 2) || (textField.text?.count == 5) {
if !(string == "") {
textField.text = (textField.text)! + "-"
}
}
return !(textField.text!.count > 7 && (string.count ) > range.length)
}

Best way to force a text field to have a single word only in swift? [duplicate]

I have a Verification ViewController, I get 4 digit verification code by SMS and I need to enter those code to login, I have created the ViewController like this
As you can see four UITextFields, I need to allow only single digit for each UITextField,
What I tried: I was trying to use shouldChangeCharactersInRange:method: , but its not getting called, I don't know what's wrong, I think because UITextFields are in UITableView so it is not working.
You can change the text field like this by using the delegate function of the text field. Initially, you need to set the delegate and the tag of each text field.
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if ((textField.text.length >= 1) && (string.length > 0))
{
NSInteger nextTag = textField.tag + 1;
// Try to find next responder
UIResponder* nextResponder = [textField.superview viewWithTag:nextTag];
if (! nextResponder)
nextResponder = [textField.superview viewWithTag:1];
if (nextResponder)
// Found next responder, so set it.
[nextResponder becomeFirstResponder];
return NO;
}
return YES;
}
Swift 2
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
// On inputing value to textfield
if (textField.text?.characters.count < 1 && string.characters.count > 0){
let nextTag = textField.tag + 1;
// get next responder
var nextResponder = textField.superview?.viewWithTag(nextTag);
if (nextResponder == nil){
nextResponder = textField.superview?.viewWithTag(1);
}
textField.text = string;
nextResponder?.becomeFirstResponder();
return false;
}
else if (textField.text?.characters.count >= 1 && string.characters.count == 0){
// on deleting value from Textfield
let previousTag = textField.tag - 1;
// get next responder
var previousResponder = textField.superview?.viewWithTag(previousTag);
if (previousResponder == nil){
previousResponder = textField.superview?.viewWithTag(1);
}
textField.text = "";
previousResponder?.becomeFirstResponder();
return false;
}
return true;
}
Swift 4
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textField.text!.count < 1 && string.count > 0{
let nextTag = textField.tag + 1
// get next responder
var nextResponder = textField.superview?.viewWithTag(nextTag)
if (nextResponder == nil){
nextResponder = textField.superview?.viewWithTag(1)
}
textField.text = string
nextResponder?.becomeFirstResponder()
return false
}
else if textField.text!.count >= 1 && string.count == 0{
// on deleting value from Textfield
let previousTag = textField.tag - 1
// get next responder
var previousResponder = textField.superview?.viewWithTag(previousTag)
if (previousResponder == nil){
previousResponder = textField.superview?.viewWithTag(1)
}
textField.text = ""
previousResponder?.becomeFirstResponder()
return false
}
return true
}
Use this code if you don't want to work with tag and it works better then above
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// On inputing value to textfield
if ((textField.text?.characters.count)! < 1 && string.characters.count > 0){
if(textField == txtOne)
{
txtTwo.becomeFirstResponder()
}
if(textField == txtTwo)
{
txtThree.becomeFirstResponder()
}
if(textField == txtThree)
{
txtFour.becomeFirstResponder()
}
textField.text = string
return false
}
else if ((textField.text?.characters.count)! >= 1 && string.characters.count == 0){
// on deleting value from Textfield
if(textField == txtTwo)
{
txtOne.becomeFirstResponder()
}
if(textField == txtThree)
{
txtTwo.becomeFirstResponder()
}
if(textField == txtFour)
{
txtThree.becomeFirstResponder()
}
textField.text = ""
return false
}
else if ((textField.text?.characters.count)! >= 1 )
{
textField.text = string
return false
}
return true
}
Swift 4
Inspired by #Anurag Soni and #Varun Naharia answers
Variant A
extension EnterConfirmationCodeTextField: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let textFieldCount = textField.text?.count else { return false }
// Сlosure
let setValueAndMoveForward = {
textField.text = string
let nextTag = textField.tag + 1
if let nextResponder = textField.superview?.viewWithTag(nextTag) {
nextResponder.becomeFirstResponder()
}
}
// Сlosure
let clearValueAndMoveBack = {
textField.text = ""
let previousTag = textField.tag - 1
if let previousResponder = textField.superview?.viewWithTag(previousTag) {
previousResponder.becomeFirstResponder()
}
}
if textFieldCount < 1 && string.count > 0 {
setValueAndMoveForward()
if textField.tag == 4 {
print("Do something")
}
return false
} else if textFieldCount >= 1 && string.count == 0 {
clearValueAndMoveBack()
return false
} else if textFieldCount >= 1 && string.count > 0 {
let nextTag = self.tag + 1
if let previousResponder = self.superview?.viewWithTag(nextTag) {
previousResponder.becomeFirstResponder()
if let activeTextField = previousResponder as? UITextField {
activeTextField.text = string
}
}
return false
}
return true
}
}
Variant B (a little bit another behavior):
extension EnterConfirmationCodeTextField: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let textFieldCount = textField.text?.count else { return false }
// Сlosure
let setValueAndMoveForward = {
textField.text = string
let nextTag = textField.tag + 1
if let nextResponder = textField.superview?.viewWithTag(nextTag) {
nextResponder.becomeFirstResponder()
}
}
// Сlosure
let clearValueAndMoveBack = {
textField.text = ""
let previousTag = textField.tag - 1
if let previousResponder = textField.superview?.viewWithTag(previousTag) {
previousResponder.becomeFirstResponder()
}
}
if textFieldCount < 1 && string.count > 0 {
setValueAndMoveForward()
if textField.tag == 4 {
print("Do something")
}
return false
} else if textFieldCount >= 1 && string.count == 0 {
clearValueAndMoveBack()
return false
} else if textFieldCount >= 1 {
setValueAndMoveForward()
return false
}
return true
}
}
Also, I implemented this feature:
In the case where the last textFiled is empty, I just want to switch to the previous textFiled. I tried all this methods. But as for me the method below more elegant and works like a charm:
Variant A
class EnterConfirmationCodeTextField: UITextField {
// MARK: Life cycle
override func awakeFromNib() {
super.awakeFromNib()
delegate = self
}
// MARK: Methods
override func deleteBackward() {
super.deleteBackward()
let previousTag = self.tag - 1
if let previousResponder = self.superview?.viewWithTag(previousTag) {
previousResponder.becomeFirstResponder()
if let activeTextField = previousResponder as? UITextField {
if let isEmpty = activeTextField.text?.isEmpty, !isEmpty {
activeTextField.text = String()
}
}
}
}
}
Variant B (a little bit another behavior):
class EnterConfirmationCodeTextField: UITextField {
// MARK: Life cycle
override func awakeFromNib() {
super.awakeFromNib()
delegate = self
}
// MARK: Methods
override func deleteBackward() {
super.deleteBackward()
let previousTag = self.tag - 1
if let previousResponder = self.superview?.viewWithTag(previousTag) {
previousResponder.becomeFirstResponder()
}
}
}
Assign EnterConfirmationCodeTextField for each of your textFields and set they appropriate tag value.
I have taken one Hidden text field & four imageViews for that with two images. One for Blank and other for Bullet same as iOS default.
Also set tags for four imageviews.
On Load set Focus for Pin Code
- (void)startPinCode
{
txtPinCodeLockDigits.text = #"";
for (int i = 1; i <= 4; i++) {
UIImageView *img = (UIImageView *)[self.view viewWithTag:i];
[img setImage:[UIImage imageNamed:#"Img_BG_PinCode.png"]];
}
[txtPinCodeLockDigits becomeFirstResponder];
}
Then change imageview's images as per user input and only allow four characters
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSString *result = [textField.text stringByReplacingCharactersInRange:range withString:string];
textField.text = result;
for (int i = 1; i <= 4; i++) {
UIImageView *img = (UIImageView *)[self.view viewWithTag:i];
if (i <= [result length])
[img setImage:[UIImage imageNamed:#"Img_BG_PinCode_Filled.png"]];
else
[img setImage:[UIImage imageNamed:#"Img_BG_PinCode.png"]];
}
NSLog(#"Result :: %#", result);
if ([result length] == 4) {
[self performSelector:#selector(keyGenerationForApplication:) withObject:result afterDelay:0.2];
}
return NO;
}
After Four characters call function for generated PIN Code and store it in User Defaults same as iOS default PIN settings
- (void)keyGenerationForApplication:(NSString *)pinCode
{
int appCode = [pinCode intValue];
[DefaultsValues setIntegerValueToUserDefaults:appCode ForKey:PIN_LOCK_PATTERN];
}
Here, you can again call StartPinCode method for re-confirming code.
Hopefully, it'll help you.
Thanks
It can be achieve using UITextField delegate & by setting Tag for each Textfield in increasing order (say 1 - 4), below is the delegate handler to solve the issue.
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
// On inputing value to textfield
if (textField.text?.characters.count < 1 && string.characters.count > 0){
let nextTag = textField.tag + 1;
// get next responder
var nextResponder = textField.superview?.viewWithTag(nextTag);
if (nextResponder == nil){
nextResponder = textField.superview?.viewWithTag(1);
}
textField.text = string;
nextResponder?.becomeFirstResponder();
return false;
}
else if (textField.text?.characters.count >= 1 && string.characters.count == 0){
// on deleteing value from Textfield
let previousTag = textField.tag - 1;
// get next responder
var previousResponder = textField.superview?.viewWithTag(previousTag);
if (previousResponder == nil){
previousResponder = textField.superview?.viewWithTag(1);
}
textField.text = "";
previousResponder?.becomeFirstResponder();
return false;
}
return true;
}
swift 2.3
class BankDepositsWithOTPVC: UIViewController {
let limitLength = 1
override func viewDidLoad() {
super.viewDidLoad()
}
}
// MARK: Textfield Validator
extension BankDepositsWithOTPVC : UITextFieldDelegate {
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
// On inputing value to textfield
if (textField.text?.characters.count < 1 && string.characters.count > 0){
let nextTag = textField.tag + 1;
// get next responder
let nextResponder = textField.superview?.viewWithTag(nextTag);
if (nextResponder == nil){
textField.resignFirstResponder()
// nextResponder = textField.superview?.viewWithTag(1);
}
textField.text = string;
nextResponder?.becomeFirstResponder();
return false;
}else if (textField.text?.characters.count >= 1 && string.characters.count > 0){
// maximum 1 digit
textField.text = "";
let nextTag = textField.tag + 1;
// get next responder
let nextResponder = textField.superview?.viewWithTag(nextTag);
if (nextResponder == nil){
textField.resignFirstResponder()
// nextResponder = textField.superview?.viewWithTag(1);
}
textField.text = string;
nextResponder?.becomeFirstResponder();
return false;
}
else if (textField.text?.characters.count >= 1 && string.characters.count == 0){
// on deleteing value from Textfield
let previousTag = textField.tag - 1;
// get next responder
var previousResponder = textField.superview?.viewWithTag(previousTag);
if (previousResponder == nil){
previousResponder = textField.superview?.viewWithTag(1);
}
textField.text = "";
previousResponder?.becomeFirstResponder();
return false;
}
//return true;
guard let text = textField.text else { return true }
let newLength = text.characters.count + string.characters.count - range.length
return newLength <= limitLength
}
}
Objective-C
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{
if ((textField.text.length < 1) && (string.length > 0))
{
NSInteger nextTag = textField.tag + 1;
UIResponder* nextResponder = [textField.superview viewWithTag:nextTag];
if (! nextResponder){
[textField resignFirstResponder];
}
textField.text = string;
if (nextResponder)
[nextResponder becomeFirstResponder];
return NO;
}else if ((textField.text.length >= 1) && (string.length > 0)){
//FOR MAXIMUM 1 TEXT
NSInteger nextTag = textField.tag + 1;
UIResponder* nextResponder = [textField.superview viewWithTag:nextTag];
if (! nextResponder){
[textField resignFirstResponder];
}
textField.text = string;
if (nextResponder)
[nextResponder becomeFirstResponder];
return NO;
}
else if ((textField.text.length >= 1) && (string.length == 0)){
// on deleteing value from Textfield
NSInteger prevTag = textField.tag - 1;
// Try to find prev responder
UIResponder* prevResponder = [textField.superview viewWithTag:prevTag];
if (! prevResponder){
[textField resignFirstResponder];
}
textField.text = string;
if (prevResponder)
// Found next responder, so set it.
[prevResponder becomeFirstResponder];
return NO;
}
return YES;
}
Modified Anurag Soni's answer in Swift 3.
It assumes you have outlet collection named textFields and the text fields have ordered tags set
It adds case when there's already some digit in text field and when user types something new - the digit is replaced
Input is restricted to digits only
It prevents from pasting more than one digit
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// Restrict to only digits
let aSet = NSCharacterSet(charactersIn:"0123456789").inverted
let compSepByCharInSet = string.components(separatedBy: aSet)
let numberFiltered = compSepByCharInSet.joined(separator: "")
if string != numberFiltered {
return false
}
// Get the unwrapped text
guard let text = textField.text else {
return false
}
if (text.characters.count < 1 && string.characters.count == 1) {
// New value to empty text field
textField.text = string
// Next responder
if let someTextField = (textFields.filter { $0.tag == textField.tag + 1 }).first {
someTextField.becomeFirstResponder()
} else {
view.endEditing(true)
}
return false
} else if (text.characters.count >= 1 && string.characters.count == 0){
// On deleting value from Textfield
textField.text = ""
// Previous responder
if let someTextField = (textFields.filter { $0.tag == textField.tag - 1 }).first {
someTextField.becomeFirstResponder()
} else {
view.endEditing(true)
}
return false
} else if string.characters.count == 1 {
// There's already some digit in text field
// Replace it with new one
textField.text = string
// Next responder
if let someTextField = (textFields.filter { $0.tag == textField.tag + 1 }).first {
someTextField.becomeFirstResponder()
} else {
view.endEditing(true)
}
}
return false
}
Provide the tag to the textfield like 1,2,3,4 and directly use it
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool
let next:NSInteger
if string == "" {
next = textField.tag - 1;
}
else{
next = textField.tag + 1;
}
if (textField.text?.characters.count)! >= 1 {
if textField.tag == 4 {
if string == "" {
textField.text = ""
let temptf = self.view.viewWithTag(next) as! UITextField
temptf.becomeFirstResponder()
return false
}
else{
if (textField.text?.characters.count)! > 1 {
let stringg = textField.text!
textField.text = stringg.replacingOccurrences(of: stringg, with: string)
}
return false
}
}
else{
if string == "" {
textField.text = ""
if next != 0 {
let temptf = self.view.viewWithTag(next) as! UITextField
temptf.becomeFirstResponder()
}
return false
}
else{
if (textField.text?.characters.count)! > 1 {
let stringg = textField.text!
textField.text = stringg.replacingOccurrences(of: stringg, with: string)
}
let temptf = self.view.viewWithTag(next) as! UITextField
temptf.becomeFirstResponder()
}
}
}
return true
}
Swift 4
when you input a number from the pin code into a text field, you should let a number be shown and then the next text field will become the first responder, so change the first responder after the code was in the text view
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let textField = textField as? PinCodeTextField else { return true }
if string == "" {// when the backward clicked, let it go :)
return true
}
// when textfield is not empty, well, next number
if textField.pinCode.count == textField.maxCount {
becomeFirstResponder(after: textField)
return false
}
if string.count > textField.maxCharacterCount {// the max character count should be 1
return false
}
return true
}
// now the text field has been filled with a number
func textFieldCotentDidChange(_ textField: UITextField) {
print("didchange")
guard let textField = textField as? PinCodeTextField else { return }
if textField.pinCode.count == 0 {
becomeFirstResponder(before: textField)
}
// when textfield has been filled, ok! next!
if textField.pinCode.count == textField.maxCharacterCount {
becomeFirstResponder(after: textField)
}
}
for more details and the simple demo, see this link
Try this
sample tutorial passcode lock
ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController<UITextFieldDelegate>
{
IBOutlet UITextField *txtPassword;
}
#end
ViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
txtPassword.delegate=self;
}
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSUInteger newLength = [textField.text length] + [string length] - range.length;
return (newLength > 1) ? NO : YES;
}
Just use TextFieldDelegate method and check the length of the TextField after every changes
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let newString = (textField.text! as NSString).stringByReplacingCharactersInRange(range, withString: string)
if newString.characters.count == 1
{
nextTextField.becomeFirstResponder()
return true
}
else
{
return false
}
}
I was working on a similar functionality and did it in my way. Solution below.
Swift 4
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
//For clear button pressed
//If the textfield has already text in it
if string.count == 0 {
textField.text = string
return true
}
//For First time entry into the text field
guard let text = textField.text, text.count <= 0 else {
//If user enter second character
return false
}
//For First time entry into the text field
if text.count == 0 {
textField.text = string
textField.resignFirstResponder()
self.nextResponde(tag: textField.tag)
return true
}
return false
}
//To make the next field as responder
func nextResponde(tag: Int) {
switch tag {
case self.PINTextField.tag:
guard let text = self.PINTextField1.text, text.count == 1 else {
self.PINTextField1.becomeFirstResponder()
return
}
case self.PINTextField1.tag:
guard let text = self.PINTextField2.text, text.count == 1 else {
self.PINTextField2.becomeFirstResponder()
return
}
case self.PINTextField2.tag:
guard let text = self.PINTextField3.text, text.count == 1 else {
self.PINTextField3.becomeFirstResponder()
return
}
default:
let _ = tag
}
}
Credit to #Anurag Soni, here's an alternate UX pushing onto the next field and dismissing upon completion (with layout logic also above):
-(UIView *)addSpacedTextInputToView:(UIView *)parent subtitle:(NSString *)subtitle spaces:(int)spaces atOffset:(float)offset{
float parentWidth = parent.frame.size.width;
float tfWidth = 2 * increment;
float tfHeight = 3 * increment;
float startX = 3 * increment;
float remainingWidth = parentWidth - 2 * startX - spaces * tfWidth;
float padding = remainingWidth / (float)(spaces-1);
UIView * holder = [self addViewToView:parent withFrame:CGRectMake(0, offset, parentWidth, 0)];
UIView * tfHolder = [self addViewToView:holder withFrame:CGRectMake(0, 0, parentWidth, tfHeight)];
float localX = startX;
float localY = 0.0f;
for (int n = 0; n < spaces; n++){
UITextField * tf = [UITextField new];
tf.frame = CGRectMake(localX, localY, tfWidth, tfHeight);
tf.textColor = [UIColor whiteColor];
tf.font = headerFont;
tf.textAlignment = NSTextAlignmentCenter;
tf.tintColor = [UIColor whiteColor];
tf.keyboardAppearance = UIKeyboardAppearanceDark;
tf.tag = n;
tf.delegate = self;
[tfHolder addSubview:tf];
UIView * div = [self addViewToView:tf withFrame:CGRectMake(0, tfHeight-2, tfWidth, 1)];
div.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.5f];
localX += tfWidth;
localX += padding;
}
localY += tfHeight;
localY += increment;
localY += [self addSystemLabelToView:holder colour:nil text:subtitle maxWidth:0 offset:localY].frame.size.height;
holder.frame = CGRectMake(0, offset, parent.frame.size.width, localY);
return holder;
}
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{
if (string.length > 0){
textField.text = string.uppercaseString; //optional to uppercase
UIView * div = textField.subviews.firstObject; //change div colour
div.backgroundColor = [UIColor whiteColor];
int nextTag = (int)textField.tag + 1;
UIResponder * nextResponder = [textField.superview viewWithTag:nextTag];
if (!nextResponder){
[textField resignFirstResponder];
}
if (nextResponder){
[nextResponder becomeFirstResponder];
}
}
return false;
}
Objective-c answer of Varun Naharia solution.
Solution using IBOutletCollections
#property (strong, nonatomic) IBOutletCollection(UITextField) NSArray *otpTextFields;
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if(textField.text.length < 1 && string.length > 0) {
for(int i=0; i<self.otpTextFields.count-1; i++) {
if(textField == self.otpTextFields[i]) {
UITextField* nextTextField = (UITextField*) self.otpTextFields[i+1];
[nextTextField becomeFirstResponder];
}
}
textField.text = string;
return NO;
} else if (textField.text.length >= 1 && string.length == 0) {
for(int i=(int) self.otpTextFields.count-1;i>=1;i--) {
if(textField == self.otpTextFields[i]) {
UITextField* nextTextField = (UITextField*) self.otpTextFields[i-1];
[nextTextField becomeFirstResponder];
}
}
textField.text = #"";
return NO;
} else if(textField.text.length >= 1){
textField.text = string;
return NO;
}
return YES;
}
try this : - For swift 3.0
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// On inputing value to textfield
if ((textField.text?.characters.count)! < 1 && string.characters.count > 0){
let nextTag = textField.tag + 1;
// get next responder
let nextResponder = textField.superview?.viewWithTag(nextTag);
textField.text = string;
if (nextResponder == nil){
textField.resignFirstResponder()
}
nextResponder?.becomeFirstResponder();
return false;
}
else if ((textField.text?.characters.count)! >= 1 && string.characters.count == 0){
// on deleting value from Textfield
let previousTag = textField.tag - 1;
// get next responder
var previousResponder = textField.superview?.viewWithTag(previousTag);
if (previousResponder == nil){
previousResponder = textField.superview?.viewWithTag(1);
}
textField.text = "";
previousResponder?.becomeFirstResponder();
return false;
}
return true;
}
converted Anurag Soni answer in swift 3.0
You just have to implement this method only.

Allowing single digit in UITextField in iOS

I have a Verification ViewController, I get 4 digit verification code by SMS and I need to enter those code to login, I have created the ViewController like this
As you can see four UITextFields, I need to allow only single digit for each UITextField,
What I tried: I was trying to use shouldChangeCharactersInRange:method: , but its not getting called, I don't know what's wrong, I think because UITextFields are in UITableView so it is not working.
You can change the text field like this by using the delegate function of the text field. Initially, you need to set the delegate and the tag of each text field.
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if ((textField.text.length >= 1) && (string.length > 0))
{
NSInteger nextTag = textField.tag + 1;
// Try to find next responder
UIResponder* nextResponder = [textField.superview viewWithTag:nextTag];
if (! nextResponder)
nextResponder = [textField.superview viewWithTag:1];
if (nextResponder)
// Found next responder, so set it.
[nextResponder becomeFirstResponder];
return NO;
}
return YES;
}
Swift 2
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
// On inputing value to textfield
if (textField.text?.characters.count < 1 && string.characters.count > 0){
let nextTag = textField.tag + 1;
// get next responder
var nextResponder = textField.superview?.viewWithTag(nextTag);
if (nextResponder == nil){
nextResponder = textField.superview?.viewWithTag(1);
}
textField.text = string;
nextResponder?.becomeFirstResponder();
return false;
}
else if (textField.text?.characters.count >= 1 && string.characters.count == 0){
// on deleting value from Textfield
let previousTag = textField.tag - 1;
// get next responder
var previousResponder = textField.superview?.viewWithTag(previousTag);
if (previousResponder == nil){
previousResponder = textField.superview?.viewWithTag(1);
}
textField.text = "";
previousResponder?.becomeFirstResponder();
return false;
}
return true;
}
Swift 4
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textField.text!.count < 1 && string.count > 0{
let nextTag = textField.tag + 1
// get next responder
var nextResponder = textField.superview?.viewWithTag(nextTag)
if (nextResponder == nil){
nextResponder = textField.superview?.viewWithTag(1)
}
textField.text = string
nextResponder?.becomeFirstResponder()
return false
}
else if textField.text!.count >= 1 && string.count == 0{
// on deleting value from Textfield
let previousTag = textField.tag - 1
// get next responder
var previousResponder = textField.superview?.viewWithTag(previousTag)
if (previousResponder == nil){
previousResponder = textField.superview?.viewWithTag(1)
}
textField.text = ""
previousResponder?.becomeFirstResponder()
return false
}
return true
}
Use this code if you don't want to work with tag and it works better then above
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// On inputing value to textfield
if ((textField.text?.characters.count)! < 1 && string.characters.count > 0){
if(textField == txtOne)
{
txtTwo.becomeFirstResponder()
}
if(textField == txtTwo)
{
txtThree.becomeFirstResponder()
}
if(textField == txtThree)
{
txtFour.becomeFirstResponder()
}
textField.text = string
return false
}
else if ((textField.text?.characters.count)! >= 1 && string.characters.count == 0){
// on deleting value from Textfield
if(textField == txtTwo)
{
txtOne.becomeFirstResponder()
}
if(textField == txtThree)
{
txtTwo.becomeFirstResponder()
}
if(textField == txtFour)
{
txtThree.becomeFirstResponder()
}
textField.text = ""
return false
}
else if ((textField.text?.characters.count)! >= 1 )
{
textField.text = string
return false
}
return true
}
Swift 4
Inspired by #Anurag Soni and #Varun Naharia answers
Variant A
extension EnterConfirmationCodeTextField: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let textFieldCount = textField.text?.count else { return false }
// Сlosure
let setValueAndMoveForward = {
textField.text = string
let nextTag = textField.tag + 1
if let nextResponder = textField.superview?.viewWithTag(nextTag) {
nextResponder.becomeFirstResponder()
}
}
// Сlosure
let clearValueAndMoveBack = {
textField.text = ""
let previousTag = textField.tag - 1
if let previousResponder = textField.superview?.viewWithTag(previousTag) {
previousResponder.becomeFirstResponder()
}
}
if textFieldCount < 1 && string.count > 0 {
setValueAndMoveForward()
if textField.tag == 4 {
print("Do something")
}
return false
} else if textFieldCount >= 1 && string.count == 0 {
clearValueAndMoveBack()
return false
} else if textFieldCount >= 1 && string.count > 0 {
let nextTag = self.tag + 1
if let previousResponder = self.superview?.viewWithTag(nextTag) {
previousResponder.becomeFirstResponder()
if let activeTextField = previousResponder as? UITextField {
activeTextField.text = string
}
}
return false
}
return true
}
}
Variant B (a little bit another behavior):
extension EnterConfirmationCodeTextField: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let textFieldCount = textField.text?.count else { return false }
// Сlosure
let setValueAndMoveForward = {
textField.text = string
let nextTag = textField.tag + 1
if let nextResponder = textField.superview?.viewWithTag(nextTag) {
nextResponder.becomeFirstResponder()
}
}
// Сlosure
let clearValueAndMoveBack = {
textField.text = ""
let previousTag = textField.tag - 1
if let previousResponder = textField.superview?.viewWithTag(previousTag) {
previousResponder.becomeFirstResponder()
}
}
if textFieldCount < 1 && string.count > 0 {
setValueAndMoveForward()
if textField.tag == 4 {
print("Do something")
}
return false
} else if textFieldCount >= 1 && string.count == 0 {
clearValueAndMoveBack()
return false
} else if textFieldCount >= 1 {
setValueAndMoveForward()
return false
}
return true
}
}
Also, I implemented this feature:
In the case where the last textFiled is empty, I just want to switch to the previous textFiled. I tried all this methods. But as for me the method below more elegant and works like a charm:
Variant A
class EnterConfirmationCodeTextField: UITextField {
// MARK: Life cycle
override func awakeFromNib() {
super.awakeFromNib()
delegate = self
}
// MARK: Methods
override func deleteBackward() {
super.deleteBackward()
let previousTag = self.tag - 1
if let previousResponder = self.superview?.viewWithTag(previousTag) {
previousResponder.becomeFirstResponder()
if let activeTextField = previousResponder as? UITextField {
if let isEmpty = activeTextField.text?.isEmpty, !isEmpty {
activeTextField.text = String()
}
}
}
}
}
Variant B (a little bit another behavior):
class EnterConfirmationCodeTextField: UITextField {
// MARK: Life cycle
override func awakeFromNib() {
super.awakeFromNib()
delegate = self
}
// MARK: Methods
override func deleteBackward() {
super.deleteBackward()
let previousTag = self.tag - 1
if let previousResponder = self.superview?.viewWithTag(previousTag) {
previousResponder.becomeFirstResponder()
}
}
}
Assign EnterConfirmationCodeTextField for each of your textFields and set they appropriate tag value.
I have taken one Hidden text field & four imageViews for that with two images. One for Blank and other for Bullet same as iOS default.
Also set tags for four imageviews.
On Load set Focus for Pin Code
- (void)startPinCode
{
txtPinCodeLockDigits.text = #"";
for (int i = 1; i <= 4; i++) {
UIImageView *img = (UIImageView *)[self.view viewWithTag:i];
[img setImage:[UIImage imageNamed:#"Img_BG_PinCode.png"]];
}
[txtPinCodeLockDigits becomeFirstResponder];
}
Then change imageview's images as per user input and only allow four characters
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSString *result = [textField.text stringByReplacingCharactersInRange:range withString:string];
textField.text = result;
for (int i = 1; i <= 4; i++) {
UIImageView *img = (UIImageView *)[self.view viewWithTag:i];
if (i <= [result length])
[img setImage:[UIImage imageNamed:#"Img_BG_PinCode_Filled.png"]];
else
[img setImage:[UIImage imageNamed:#"Img_BG_PinCode.png"]];
}
NSLog(#"Result :: %#", result);
if ([result length] == 4) {
[self performSelector:#selector(keyGenerationForApplication:) withObject:result afterDelay:0.2];
}
return NO;
}
After Four characters call function for generated PIN Code and store it in User Defaults same as iOS default PIN settings
- (void)keyGenerationForApplication:(NSString *)pinCode
{
int appCode = [pinCode intValue];
[DefaultsValues setIntegerValueToUserDefaults:appCode ForKey:PIN_LOCK_PATTERN];
}
Here, you can again call StartPinCode method for re-confirming code.
Hopefully, it'll help you.
Thanks
It can be achieve using UITextField delegate & by setting Tag for each Textfield in increasing order (say 1 - 4), below is the delegate handler to solve the issue.
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
// On inputing value to textfield
if (textField.text?.characters.count < 1 && string.characters.count > 0){
let nextTag = textField.tag + 1;
// get next responder
var nextResponder = textField.superview?.viewWithTag(nextTag);
if (nextResponder == nil){
nextResponder = textField.superview?.viewWithTag(1);
}
textField.text = string;
nextResponder?.becomeFirstResponder();
return false;
}
else if (textField.text?.characters.count >= 1 && string.characters.count == 0){
// on deleteing value from Textfield
let previousTag = textField.tag - 1;
// get next responder
var previousResponder = textField.superview?.viewWithTag(previousTag);
if (previousResponder == nil){
previousResponder = textField.superview?.viewWithTag(1);
}
textField.text = "";
previousResponder?.becomeFirstResponder();
return false;
}
return true;
}
swift 2.3
class BankDepositsWithOTPVC: UIViewController {
let limitLength = 1
override func viewDidLoad() {
super.viewDidLoad()
}
}
// MARK: Textfield Validator
extension BankDepositsWithOTPVC : UITextFieldDelegate {
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
// On inputing value to textfield
if (textField.text?.characters.count < 1 && string.characters.count > 0){
let nextTag = textField.tag + 1;
// get next responder
let nextResponder = textField.superview?.viewWithTag(nextTag);
if (nextResponder == nil){
textField.resignFirstResponder()
// nextResponder = textField.superview?.viewWithTag(1);
}
textField.text = string;
nextResponder?.becomeFirstResponder();
return false;
}else if (textField.text?.characters.count >= 1 && string.characters.count > 0){
// maximum 1 digit
textField.text = "";
let nextTag = textField.tag + 1;
// get next responder
let nextResponder = textField.superview?.viewWithTag(nextTag);
if (nextResponder == nil){
textField.resignFirstResponder()
// nextResponder = textField.superview?.viewWithTag(1);
}
textField.text = string;
nextResponder?.becomeFirstResponder();
return false;
}
else if (textField.text?.characters.count >= 1 && string.characters.count == 0){
// on deleteing value from Textfield
let previousTag = textField.tag - 1;
// get next responder
var previousResponder = textField.superview?.viewWithTag(previousTag);
if (previousResponder == nil){
previousResponder = textField.superview?.viewWithTag(1);
}
textField.text = "";
previousResponder?.becomeFirstResponder();
return false;
}
//return true;
guard let text = textField.text else { return true }
let newLength = text.characters.count + string.characters.count - range.length
return newLength <= limitLength
}
}
Objective-C
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{
if ((textField.text.length < 1) && (string.length > 0))
{
NSInteger nextTag = textField.tag + 1;
UIResponder* nextResponder = [textField.superview viewWithTag:nextTag];
if (! nextResponder){
[textField resignFirstResponder];
}
textField.text = string;
if (nextResponder)
[nextResponder becomeFirstResponder];
return NO;
}else if ((textField.text.length >= 1) && (string.length > 0)){
//FOR MAXIMUM 1 TEXT
NSInteger nextTag = textField.tag + 1;
UIResponder* nextResponder = [textField.superview viewWithTag:nextTag];
if (! nextResponder){
[textField resignFirstResponder];
}
textField.text = string;
if (nextResponder)
[nextResponder becomeFirstResponder];
return NO;
}
else if ((textField.text.length >= 1) && (string.length == 0)){
// on deleteing value from Textfield
NSInteger prevTag = textField.tag - 1;
// Try to find prev responder
UIResponder* prevResponder = [textField.superview viewWithTag:prevTag];
if (! prevResponder){
[textField resignFirstResponder];
}
textField.text = string;
if (prevResponder)
// Found next responder, so set it.
[prevResponder becomeFirstResponder];
return NO;
}
return YES;
}
Modified Anurag Soni's answer in Swift 3.
It assumes you have outlet collection named textFields and the text fields have ordered tags set
It adds case when there's already some digit in text field and when user types something new - the digit is replaced
Input is restricted to digits only
It prevents from pasting more than one digit
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// Restrict to only digits
let aSet = NSCharacterSet(charactersIn:"0123456789").inverted
let compSepByCharInSet = string.components(separatedBy: aSet)
let numberFiltered = compSepByCharInSet.joined(separator: "")
if string != numberFiltered {
return false
}
// Get the unwrapped text
guard let text = textField.text else {
return false
}
if (text.characters.count < 1 && string.characters.count == 1) {
// New value to empty text field
textField.text = string
// Next responder
if let someTextField = (textFields.filter { $0.tag == textField.tag + 1 }).first {
someTextField.becomeFirstResponder()
} else {
view.endEditing(true)
}
return false
} else if (text.characters.count >= 1 && string.characters.count == 0){
// On deleting value from Textfield
textField.text = ""
// Previous responder
if let someTextField = (textFields.filter { $0.tag == textField.tag - 1 }).first {
someTextField.becomeFirstResponder()
} else {
view.endEditing(true)
}
return false
} else if string.characters.count == 1 {
// There's already some digit in text field
// Replace it with new one
textField.text = string
// Next responder
if let someTextField = (textFields.filter { $0.tag == textField.tag + 1 }).first {
someTextField.becomeFirstResponder()
} else {
view.endEditing(true)
}
}
return false
}
Provide the tag to the textfield like 1,2,3,4 and directly use it
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool
let next:NSInteger
if string == "" {
next = textField.tag - 1;
}
else{
next = textField.tag + 1;
}
if (textField.text?.characters.count)! >= 1 {
if textField.tag == 4 {
if string == "" {
textField.text = ""
let temptf = self.view.viewWithTag(next) as! UITextField
temptf.becomeFirstResponder()
return false
}
else{
if (textField.text?.characters.count)! > 1 {
let stringg = textField.text!
textField.text = stringg.replacingOccurrences(of: stringg, with: string)
}
return false
}
}
else{
if string == "" {
textField.text = ""
if next != 0 {
let temptf = self.view.viewWithTag(next) as! UITextField
temptf.becomeFirstResponder()
}
return false
}
else{
if (textField.text?.characters.count)! > 1 {
let stringg = textField.text!
textField.text = stringg.replacingOccurrences(of: stringg, with: string)
}
let temptf = self.view.viewWithTag(next) as! UITextField
temptf.becomeFirstResponder()
}
}
}
return true
}
Swift 4
when you input a number from the pin code into a text field, you should let a number be shown and then the next text field will become the first responder, so change the first responder after the code was in the text view
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let textField = textField as? PinCodeTextField else { return true }
if string == "" {// when the backward clicked, let it go :)
return true
}
// when textfield is not empty, well, next number
if textField.pinCode.count == textField.maxCount {
becomeFirstResponder(after: textField)
return false
}
if string.count > textField.maxCharacterCount {// the max character count should be 1
return false
}
return true
}
// now the text field has been filled with a number
func textFieldCotentDidChange(_ textField: UITextField) {
print("didchange")
guard let textField = textField as? PinCodeTextField else { return }
if textField.pinCode.count == 0 {
becomeFirstResponder(before: textField)
}
// when textfield has been filled, ok! next!
if textField.pinCode.count == textField.maxCharacterCount {
becomeFirstResponder(after: textField)
}
}
for more details and the simple demo, see this link
Try this
sample tutorial passcode lock
ViewController.h
#import <UIKit/UIKit.h>
#interface ViewController : UIViewController<UITextFieldDelegate>
{
IBOutlet UITextField *txtPassword;
}
#end
ViewController.m
- (void)viewDidLoad
{
[super viewDidLoad];
txtPassword.delegate=self;
}
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
NSUInteger newLength = [textField.text length] + [string length] - range.length;
return (newLength > 1) ? NO : YES;
}
Just use TextFieldDelegate method and check the length of the TextField after every changes
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let newString = (textField.text! as NSString).stringByReplacingCharactersInRange(range, withString: string)
if newString.characters.count == 1
{
nextTextField.becomeFirstResponder()
return true
}
else
{
return false
}
}
I was working on a similar functionality and did it in my way. Solution below.
Swift 4
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
//For clear button pressed
//If the textfield has already text in it
if string.count == 0 {
textField.text = string
return true
}
//For First time entry into the text field
guard let text = textField.text, text.count <= 0 else {
//If user enter second character
return false
}
//For First time entry into the text field
if text.count == 0 {
textField.text = string
textField.resignFirstResponder()
self.nextResponde(tag: textField.tag)
return true
}
return false
}
//To make the next field as responder
func nextResponde(tag: Int) {
switch tag {
case self.PINTextField.tag:
guard let text = self.PINTextField1.text, text.count == 1 else {
self.PINTextField1.becomeFirstResponder()
return
}
case self.PINTextField1.tag:
guard let text = self.PINTextField2.text, text.count == 1 else {
self.PINTextField2.becomeFirstResponder()
return
}
case self.PINTextField2.tag:
guard let text = self.PINTextField3.text, text.count == 1 else {
self.PINTextField3.becomeFirstResponder()
return
}
default:
let _ = tag
}
}
Credit to #Anurag Soni, here's an alternate UX pushing onto the next field and dismissing upon completion (with layout logic also above):
-(UIView *)addSpacedTextInputToView:(UIView *)parent subtitle:(NSString *)subtitle spaces:(int)spaces atOffset:(float)offset{
float parentWidth = parent.frame.size.width;
float tfWidth = 2 * increment;
float tfHeight = 3 * increment;
float startX = 3 * increment;
float remainingWidth = parentWidth - 2 * startX - spaces * tfWidth;
float padding = remainingWidth / (float)(spaces-1);
UIView * holder = [self addViewToView:parent withFrame:CGRectMake(0, offset, parentWidth, 0)];
UIView * tfHolder = [self addViewToView:holder withFrame:CGRectMake(0, 0, parentWidth, tfHeight)];
float localX = startX;
float localY = 0.0f;
for (int n = 0; n < spaces; n++){
UITextField * tf = [UITextField new];
tf.frame = CGRectMake(localX, localY, tfWidth, tfHeight);
tf.textColor = [UIColor whiteColor];
tf.font = headerFont;
tf.textAlignment = NSTextAlignmentCenter;
tf.tintColor = [UIColor whiteColor];
tf.keyboardAppearance = UIKeyboardAppearanceDark;
tf.tag = n;
tf.delegate = self;
[tfHolder addSubview:tf];
UIView * div = [self addViewToView:tf withFrame:CGRectMake(0, tfHeight-2, tfWidth, 1)];
div.backgroundColor = [[UIColor whiteColor] colorWithAlphaComponent:0.5f];
localX += tfWidth;
localX += padding;
}
localY += tfHeight;
localY += increment;
localY += [self addSystemLabelToView:holder colour:nil text:subtitle maxWidth:0 offset:localY].frame.size.height;
holder.frame = CGRectMake(0, offset, parent.frame.size.width, localY);
return holder;
}
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string{
if (string.length > 0){
textField.text = string.uppercaseString; //optional to uppercase
UIView * div = textField.subviews.firstObject; //change div colour
div.backgroundColor = [UIColor whiteColor];
int nextTag = (int)textField.tag + 1;
UIResponder * nextResponder = [textField.superview viewWithTag:nextTag];
if (!nextResponder){
[textField resignFirstResponder];
}
if (nextResponder){
[nextResponder becomeFirstResponder];
}
}
return false;
}
Objective-c answer of Varun Naharia solution.
Solution using IBOutletCollections
#property (strong, nonatomic) IBOutletCollection(UITextField) NSArray *otpTextFields;
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
{
if(textField.text.length < 1 && string.length > 0) {
for(int i=0; i<self.otpTextFields.count-1; i++) {
if(textField == self.otpTextFields[i]) {
UITextField* nextTextField = (UITextField*) self.otpTextFields[i+1];
[nextTextField becomeFirstResponder];
}
}
textField.text = string;
return NO;
} else if (textField.text.length >= 1 && string.length == 0) {
for(int i=(int) self.otpTextFields.count-1;i>=1;i--) {
if(textField == self.otpTextFields[i]) {
UITextField* nextTextField = (UITextField*) self.otpTextFields[i-1];
[nextTextField becomeFirstResponder];
}
}
textField.text = #"";
return NO;
} else if(textField.text.length >= 1){
textField.text = string;
return NO;
}
return YES;
}
try this : - For swift 3.0
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// On inputing value to textfield
if ((textField.text?.characters.count)! < 1 && string.characters.count > 0){
let nextTag = textField.tag + 1;
// get next responder
let nextResponder = textField.superview?.viewWithTag(nextTag);
textField.text = string;
if (nextResponder == nil){
textField.resignFirstResponder()
}
nextResponder?.becomeFirstResponder();
return false;
}
else if ((textField.text?.characters.count)! >= 1 && string.characters.count == 0){
// on deleting value from Textfield
let previousTag = textField.tag - 1;
// get next responder
var previousResponder = textField.superview?.viewWithTag(previousTag);
if (previousResponder == nil){
previousResponder = textField.superview?.viewWithTag(1);
}
textField.text = "";
previousResponder?.becomeFirstResponder();
return false;
}
return true;
}
converted Anurag Soni answer in swift 3.0
You just have to implement this method only.

Resources