I have four textFields and each takes only one character, similar to the lock screen. I can input and move from one textField to next textField.
Problem is when I want to delete and move to previous textField, I don't know when the delete button is clicked.
I'm using:
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool
to input and move from one textField to another.
How do I know when the delete key is pressed?
edit/update:
Xcode 11 • Swift 5.2 or later
Follow up on this question and Swift 5 syntax can be found on this post
Original answer
Swift 1.x
Following up Istvan answer, you need to post a notification when the deleteBackward is pressed:
class DigitField: UITextField {
override func deleteBackward() {
super.deleteBackward()
NSNotificationCenter.defaultCenter().postNotificationName("deletePressed", object: nil)
}
}
Then inside viewDidLoad() you add an observer as follow:
NSNotificationCenter.defaultCenter().addObserver(self, selector: "goPrevious", name: "deletePressed", object: nil)
and your method:
func goPrevious() {
if secondDigit.isFirstResponder() {
secondDigit.enabled = false
firstDigit.enabled = true
firstDigit.becomeFirstResponder()
} else if thirdDigit.isFirstResponder() {
thirdDigit.enabled = false
secondDigit.enabled = true
secondDigit.becomeFirstResponder()
} else if fourthDigit.isFirstResponder() {
fourthDigit.enabled = false
thirdDigit.enabled = true
thirdDigit.becomeFirstResponder()
}
}
Select your text field and connect it to your DigitField
You need to connect each text field to an IBAction (using sent events editing changed)
The view controller code should look like this:
import UIKit
class ViewController: UIViewController {
#IBOutlet weak var firstDigit: UITextField!
#IBOutlet weak var secondDigit: UITextField!
#IBOutlet weak var thirdDigit: UITextField!
#IBOutlet weak var fourthDigit: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: "goPrevious", name: "deletePressed", object: nil)
firstDigit.secureTextEntry = true
secondDigit.secureTextEntry = true
thirdDigit.secureTextEntry = true
fourthDigit.secureTextEntry = true
firstDigit.keyboardType = .DecimalPad
secondDigit.keyboardType = .DecimalPad
thirdDigit.keyboardType = .DecimalPad
fourthDigit.keyboardType = .DecimalPad
firstDigit.becomeFirstResponder()
secondDigit.enabled = false
thirdDigit.enabled = false
fourthDigit.enabled = false
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func goPrevious() {
if secondDigit.isFirstResponder() {
secondDigit.enabled = false
firstDigit.enabled = true
firstDigit.becomeFirstResponder()
} else if thirdDigit.isFirstResponder() {
thirdDigit.enabled = false
secondDigit.enabled = true
secondDigit.becomeFirstResponder()
} else if fourthDigit.isFirstResponder() {
fourthDigit.enabled = false
thirdDigit.enabled = true
thirdDigit.becomeFirstResponder()
}
}
// You need to connect each text field to an IBAction (using sent events editing changed) –
#IBAction func firstChanged(sender: UITextField) {
if let digitOne = sender.text.toInt() {
println(digitOne)
sender.enabled = false
secondDigit.enabled = true
secondDigit.becomeFirstResponder()
} else {
sender.text = ""
}
}
#IBAction func secondChanged(sender: UITextField) {
if let digitTwo = sender.text.toInt() {
println(digitTwo)
sender.enabled = false
thirdDigit.enabled = true
thirdDigit.becomeFirstResponder()
} else {
sender.text = ""
}
}
#IBAction func thirdChanged(sender: UITextField) {
if let digitThree = sender.text.toInt() {
println(digitThree)
sender.enabled = false
fourthDigit.enabled = true
fourthDigit.becomeFirstResponder()
} else {
sender.text = ""
}
}
#IBAction func fourthChanged(sender: UITextField) {
if let digitFour = sender.text.toInt() {
println(digitFour)
sender.enabled = false
} else {
sender.text = ""
}
}
}
Subclass UITextField as follows:
import UIKit
class MyTextField: UITextField {
override func deleteBackward() {
super.deleteBackward()
println("Delete button was tapped")
}
}
Then change the class of your text field to MyTextField. That should do it!
There is a possible solution in this answer to a similar question. You can subclass your UITextField and then override this method in your subclass:
(BOOL)keyboardInputShouldDelete:(UITextField *)textField
Event this code is in Objective-C, it should show you the proper use of this method.
Use replacementString in function to know whats happening in UItextField
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
if (string.length == 0) {
//delete
} else {
//any other key
}
return YES;
}
I hope my answer will help you:
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let char = string.cStringUsingEncoding(NSUTF8StringEncoding)!
let isBackSpace = strcmp(char, "\\b")
if (isBackSpace == -92) {
println("Backspace was pressed")
}
return true
}
Related
so i make this otp screen but i have some catch,
i make this otp screen with bunch of uitextfield and i make the logic of it, but i just cant delete on of the num in the textfield that i make
the textfield wont delete when i fill like the first 2 of my num, even i pressess backButton it wont work.....but it will work when i fill the whole num of textfield, in my case is six.
so i have to fill all six of the number and i can delete the number from the textfield, it wont work if only half fill in the textfield.
heres 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 {
txtOTP5.becomeFirstResponder()
}
if textField == txtOTP5{
txtOTP6.becomeFirstResponder()
}
if textField == txtOTP6{
txtOTP6.resignFirstResponder()
}
textField.text = string
return false
}else if ((textField.text?.count)! >= 1) && (string.count == 0) {
if textField == txtOTP2{
txtOTP1.becomeFirstResponder()
}
if textField == txtOTP3{
txtOTP2.becomeFirstResponder()
}
if textField == txtOTP4{
txtOTP3.becomeFirstResponder()
}
if textField == txtOTP5{
txtOTP4.becomeFirstResponder()
}
if textField == txtOTP6{
txtOTP5.becomeFirstResponder()
}
if textField == txtOTP1{
txtOTP1.resignFirstResponder()
}
textField.text = ""
return false
}
else if (textField.text?.count)! >= 1 {
textField.text = string
return false
}
return true
}
thats the code i use to make the otp uitextField logic......please tell me i know theres something wrong with my logic, thanks.
i watch a tutorial to make this otp screen in this vid
https://www.youtube.com/watch?v=gZnBXh0TRO8
and according to the maker, he said that to fix this issue i just need to "set user interactions for textfield false and make first textfield first responder", i think i just did that but i maybe i did it wrong....
i really need to fix this guys, thanks.
Instead of fixing that code I prefer to create a custom text field that would inform when the deleteBackward key is pressed. So first subclass a UITextField:
import UIKit
class SingleDigitField: UITextField {
// create a boolean property to hold the deleteBackward info
var pressedDelete = false
// customize the text field as you wish
override func willMove(toSuperview newSuperview: UIView?) {
keyboardType = .numberPad
textAlignment = .center
backgroundColor = .blue
isSecureTextEntry = true
isUserInteractionEnabled = false
}
// hide cursor
override func caretRect(for position: UITextPosition) -> CGRect { .zero }
// hide selection
override func selectionRects(for range: UITextRange) -> [UITextSelectionRect] { [] }
// disable copy paste
override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { false }
// override deleteBackward method, set the property value to true and send an action for editingChanged
override func deleteBackward() {
pressedDelete = true
sendActions(for: .editingChanged)
}
}
Now in your ViewCOntroller:
import UIKit
class ViewController: UIViewController {
// connect the textfields outlets
#IBOutlet weak var firstDigitField: SingleDigitField!
#IBOutlet weak var secondDigitField: SingleDigitField!
#IBOutlet weak var thirdDigitField: SingleDigitField!
#IBOutlet weak var fourthDigitField: SingleDigitField!
override func viewDidLoad() {
super.viewDidLoad()
// add a target for editing changed for each field
[firstDigitField,secondDigitField,thirdDigitField,fourthDigitField].forEach {
$0?.addTarget(self, action: #selector(editingChanged), for: .editingChanged)
}
// make the firsDigitField the first responder
firstDigitField.isUserInteractionEnabled = true
firstDigitField.becomeFirstResponder()
}
// here you control what happens to each change that occurs to the fields
#objc func editingChanged(_ textField: SingleDigitField) {
// check if the deleteBackwards key was pressed
if textField.pressedDelete {
// reset its state
textField.pressedDelete = false
// if the field has text empty its content
if textField.hasText {
textField.text = ""
} else {
// otherwise switch the field, resign the first responder and activate the previous field and empty its contents
switch textField {
case secondDigitField, thirdDigitField, fourthDigitField:
textField.resignFirstResponder()
textField.isUserInteractionEnabled = false
switch textField {
case secondDigitField:
firstDigitField.isUserInteractionEnabled = true
firstDigitField.becomeFirstResponder()
firstDigitField.text = ""
case thirdDigitField:
secondDigitField.isUserInteractionEnabled = true
secondDigitField.becomeFirstResponder()
secondDigitField.text = ""
case fourthDigitField:
thirdDigitField.isUserInteractionEnabled = true
thirdDigitField.becomeFirstResponder()
thirdDigitField.text = ""
default:
break
}
default: break
}
}
}
// make sure there is only one character and it is a number otherwise delete its contents
guard textField.text?.count == 1, textField.text?.last?.isWholeNumber == true else {
textField.text = ""
return
}
// switch the textField, resign the first responder and make the next field active
switch textField {
case firstDigitField, secondDigitField, thirdDigitField:
textField.resignFirstResponder()
textField.isUserInteractionEnabled = false
switch textField {
case firstDigitField:
secondDigitField.isUserInteractionEnabled = true
secondDigitField.becomeFirstResponder()
case secondDigitField:
thirdDigitField.isUserInteractionEnabled = true
thirdDigitField.becomeFirstResponder()
case thirdDigitField:
fourthDigitField.isUserInteractionEnabled = true
fourthDigitField.becomeFirstResponder()
default: break
}
case fourthDigitField:
fourthDigitField.resignFirstResponder()
default: break
}
}
}
Xcode 12 sample project
I have the following code to disable a button as long a textfield is empty:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let text = (textField.text! as NSString).replacingCharacters(in: range, with: string)
if !text.isEmpty{
addButton.isEnabled = true
} else {
addButton.isEnabled = false
}
return true
}
It works fine, but now that I have 3 textfields, I want the button only to be enabled, if all textfields are not empty. So far, as soon as one textfield is filled in, the button is being enabled.
How can I adjust my code to do so?
Add target to all textfields for .editingChanged event, and check if any textfield is empty. If all text fields contain text enable the button else disable the button.
class TestViewController: UIViewController, UITextFieldDelegate {
let addButton = UIButton()
let textField1 = UITextField()
let textField2 = UITextField()
let textField3 = UITextField()
override func viewDidLoad() {
super.viewDidLoad()
textField1.addTarget(self, action: #selector(textChanged(_:)), for: .editingChanged)
textField2.addTarget(self, action: #selector(textChanged(_:)), for: .editingChanged)
textField3.addTarget(self, action: #selector(textChanged(_:)), for: .editingChanged)
}
#objc func textChanged(_ textField: UITextField) {
addButton.isEnabled = [textField1, textField2, textField3].contains { $0.text!.isEmpty }
}
}
As your requirement first you have to create outlet for each textfield and you can enable the button as,
#IBAction func textFieldValueChanged(_ sender: Any)
{
if firstTextField.text != "" && secondTextField.text != "" && thirdTextField.text != "" {
addButton.isEnabled = true
} else {
addButton.isEnabled = false
}
return true
And connect each textfield with the above action for valueChanged event
Well, I don't think the accepted answer is an elegant solution to this problem.
I would suggest to add the following observer in your viewDidLoad:
NotificationCenter.default.addObserver(self, selector: #selector(validate), name: UITextField.textDidChangeNotification, object: nil)
Then define the selector:
#objc func validate(){
var filteredArray = [textFieldOne,textFieldTwo,textFieldThree,textFieldFour].filter { $0?.text == "" }
if !filteredArray.isEmpty {
button.isHidden = true
} else {
button.isHidden = false
}
}
I am trying to create otp textfield using five textfield.All working fine if you add top, but issue is occurred when user try to add textfield empty and trying to backspace and it was not call any delegate method of UItextfiled which I already added.
I tried this :-
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let char = string.cStringUsingEncoding(NSUTF8StringEncoding)!
let isBackSpace = strcmp(char, "\\b")
if (isBackSpace == -92) {
println("Backspace was pressed")
}
return true
}
but it's called when textfield is not empty.
For example :-
In below screen shot add 1 and on two different textfield and third one is empty but when I try to backspace it's need to go in second textfield(third is field is empty) this is what I was facing issue from mine side.
Thanks
followed by #Marmik Shah and #Prashant Tukadiya answer here I add my answer , for quick answer I taken the some code from here
step 1 :
create the IBOutletCollection for your all textfields as well as don't forget to set the tag in all textfields in the sequence order, for e.g [1,2,3,4,5,6]
class ViewController: UIViewController{
#IBOutlet var OTPTxtFields: [MyTextField]! // as well as set the tag for textfield in the sequence order
override func viewDidLoad() {
super.viewDidLoad()
//change button color and other options
OTPTxtFields.forEach { $0.textColor = .red; $0.backspaceTextFieldDelegate = self }
OTPTxtFields.first.becomeFirstResponder()
}
step 2 :
in your current page UITextField delegate method
extension ViewController : UITextFieldDelegate, MyTextFieldDelegate {
func textFieldDidEnterBackspace(_ textField: MyTextField) {
guard let index = OTPTxtFields.index(of: textField) else {
return
}
if index > 0 {
OTPTxtFields[index - 1].becomeFirstResponder()
} else {
view.endEditing(true)
}
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let newString = ((textField.text)! as NSString).replacingCharacters(in: range, with: string)
if newString.count < 2 && !newString.isEmpty {
textFieldShouldReturnSingle(textField, newString : newString)
// return false
}
return newString.count < 2 || string == ""
//return true
}
override public func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
if action == #selector(copy(_:)) || action == #selector(paste(_:)) {
return false
}
return true
}
func textFieldShouldReturnSingle(_ textField: UITextField, newString : String)
{
let nextTag: Int = textField.tag + 1
textField.text = newString
let nextResponder: UIResponder? = textField.superview?.viewWithTag(nextTag)
if let nextR = nextResponder
{
// Found next responder, so set it.
nextR.becomeFirstResponder()
}
else
{
// Not found, so remove keyboard.
textField.resignFirstResponder()
callOTPValidate()
}
}
}
Step 3:
create the textfield class for access the backward function
class MyTextField: UITextField {
weak var myTextFieldDelegate: MyTextFieldDelegate?
override func deleteBackward() {
if text?.isEmpty ?? false {
myTextFieldDelegate?.textFieldDidEnterBackspace(self)
}
super.deleteBackward()
}
}
protocol MyTextFieldDelegate: class {
func textFieldDidEnterBackspace(_ textField: MyTextField)
}
step - 4
finally follow the #Marmik Shah answer for custom class for your UITextField
Step 5
get the values from each textfield use this
func callOTPValidate(){
var texts: [String] = []
OTPTxtFields.forEach { texts.append($0.text!)}
sentOTPOption(currentText: texts.reduce("", +))
}
func sentOTPOption(currentText: String) {
print("AllTextfieldValue == \(currentText)")
}
You can override the function deleteBackward()
Create a new Class that inherits UITextField and trigger and EditingEnd event.
class MyTextField: UITextField {
override func deleteBackward() {
super.deleteBackward()
print("Backspace");
self.endEditing(true);
}
}
Then, in your Storyboard, add a custom class for the UITextField
Next, in your view controller, in the editingEnd action, you can switch the textfield. For this to work, you will need to set a tag value for each of your textfield.
For example, you have two text fields, tfOne and tfTwo.
tfOne.tag = 1; tfTwo.tag = 2
Now, if currently you are editing tfTwo and backspace is clicked, then you set the currently editing text field to tfOne
class ViewController: UIViewController {
#IBOutlet weak var tfOne: MyTextField!
#IBOutlet weak var tfTwo: MyTextField!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func editingEnded(_ sender: UITextField) {
// UITextField editing ended
if(sender.tag == 2) {
self.tfOne.becomeFirstResponder();
}
}
}
You can give tag to your textfield in sequence like 101,102,103,104,105.
when backspace tapped. check the length of string is equal to 0. then goto textfield.tag - 1 until you get first textfield.like if you are on textfield 102 then goto textfield 102-1 = 101.
Same as when enter any character goto next textfield until you reach to last textfield like if you are on textfield 102 then goto textfield 102+1 = 103.
You can use (self.view.viewWithTag(yourTag) as? UITextField)?.becomeFirstResponder()
I don't have system with me so couldn't able to post code
I have login controller where it has two textFields:
Access Card
Password
The Max Length for the access card is 9 and once the user type the ninth number, it should appear on the access card filed then the cursor needs to move to the password field.
In my code, the cursor is moving when the user clicks to enter the ninth number but the number doesn't appear and the cursor moves to the password field.
For example: I want to enter "123456789" as access card. Once I click "9" it doesn't appear but the cursor moves to password field:
LoginController.swift:
let ACCESSCARD_MAXLENGTH = 9
let PASSWORD_MAXLENGTH = 12
var AccessCardtextFieldLength = 0
var PasswordTextFieldLength = 0
class LoginViewController: UIViewController , UITextFieldDelegate {
#IBOutlet weak var AccessCardTextField: UITextField!
#IBOutlet weak var PasswordTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
// calling the function that initialize textFields
initializeTextFields()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
// function is used to initialize textFields
func initializeTextFields () {
// To set the focus on the access card once the view load.
AccessCardTextField.becomeFirstResponder()
// This must be defined so we can apply the text field functions on it
AccessCardTextField.delegate = self
PasswordTextField.delegate = self
// Define the keyboard type of the textFields.
AccessCardTextField.keyboardType = UIKeyboardType.NumberPad
PasswordTextField.keyboardType = UIKeyboardType.ASCIICapable
}
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
AccessCardtextFieldLength = (textField.text?.characters.count)! + string.characters.count
PasswordTextFieldLength = (textField.text?.characters.count)! + string.characters.count
if (textField == AccessCardTextField){
for i in 0..<ACCESSCARD_MAXLENGTH{
if (AccessCardtextFieldLength == ACCESSCARD_MAXLENGTH){
PasswordTextField.becomeFirstResponder()
}
else{
return true
}
return false
}
}
if (textField == PasswordTextField){
return PasswordTextFieldLength <= PASSWORD_MAXLENGTH ? true : false
}
return true
}
}
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool will only update when it return true. In this case you are changing the firstResponder therefore it is not updated.
My suggestion is to use add target for this case. This is what you can do:
override func viewDidLoad() {
super.viewDidLoad()
// calling the function that initialize textFields
initializeTextFields()
accessCardTextField.addTarget(self, action: #selector(LoginViewController.accessCardTextFieldChanged(_:)), forControlEvents: .EditingChanged)
}
func accessCardTextFieldChanged(textField: UITextField) {
if textField.text?.characters.count == ACCESSCARD_MAXLENGTH {
modelTextField.becomeFirstResponder()
}
}
This way, it save you quite a few line of code. Most importantly, only accessCardTextField changed will be call. You could do another function to check your password textfield length separately. Also, i renamed from AccessCardTextField to accessCardTextField. It is recommended to have variable starting with lower case.
Not very sure if this works, but try edit your if statement contents with this.
if (AccessCardtextFieldLength == ACCESSCARD_MAXLENGTH){
if (textField == AccessCardTextField) {
textField.resignFirstResponder()
PasswordTextField.becomeFirstResponder()
}
}
In this condition
if (AccessCardtextFieldLength == ACCESSCARD_MAXLENGTH){
PasswordTextField.becomeFirstResponder()
**return true**
}
else{
return true
}
you returning flase that's why it doesn't show your last Character.
When I tap on this UITextFieldmentioned on screen below, a numpad keyboard appears, the problem is that when I start tap on number, my UITextField do not update, stay with no number, only placeholder.
What I need to do?
Thanks
ADDING CODE!
#IBOutlet weak var salarioTextField: UITextField!
override func viewDidLoad()
{
super.viewDidLoad()
updateView()
salarioTextField.delegate = self
}
func textFieldShouldBeginEditing(textField: UITextField) -> Bool
{
if textField == feriasTextField
{
feriasTextField.inputView = feriasPicker
}
else if textField == salarioTextField
{
salarioTextField.keyboardType = UIKeyboardType.NumberPad
}
else if textField == inicioTextField
{
textField.inputView = datePicker
textField.inputAccessoryView = toolbar
datePicker.date = inicioDate
}
else if textField == motivoTextField
{
motivoTextField.inputView = motivoPicker
}
else
{
textField.inputView = datePicker
textField.inputAccessoryView = toolbar
datePicker.date = fimDate
}
return true
}
func textFieldDidBeginEditing(textField: UITextField)
{
backgroundScrollView.scrollEnabled = true
let scrollSize = CGSizeMake(view.frame.width, view.frame.height)
backgroundScrollView.contentSize = scrollSize
activeTextField = textField
activeTextField?.becomeFirstResponder()
}
func textFieldDidEndEditing(textField: UITextField)
{
backgroundScrollView.contentSize = CGSizeMake(0, 800)
backgroundScrollView.scrollEnabled = true
}
Please ignore the others UITextField.
Make sure you have enabled user interaction for the field.
If you implement the delegate method textViewDidBeginEditing, you can put a breakpoint or a print statement here to see if it triggers.
You can try calling becomeFirstResponder on the field.
I just figured out what was happening.
I was returning false to the method below.
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
return true;
}
Just changed it to true and it's working.
Thanks for your help.