Auto switching between UITextFields with Swift - ios

I have 4 UITextFields in one UIView like this:
each UITextField limited to 4 characters. i want implement a auto switching between UITextFields with count characters of each UITextField.
i use shouldChangeCharactersIn for limitation characters:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let maxLength = 4
var newString = String()
let currentString: NSString = textField.text! as NSString
newString = currentString.replacingCharacters(in: range, with: string)
return newString.length <= maxLength
}
and this is my switching implement:
func textFieldDidChange(_ textField: UITextField){
let text = textField.text
if text?.utf16.count == 4 {
switch textField {
case firstPartTextField:
secondPartTextField.becomeFirstResponder()
case secondPartTextField:
thirdPartTextField.becomeFirstResponder()
case thirdPartTextField:
fourthPartTextField.becomeFirstResponder()
default:
break
}
}
}
my problem is switch to previous UITextField in deleting texts, i can not detect backSpace event when UITextField is empty.

Add target for textField in viewDidLoad()
firstTxt.addTarget(self, action: #selector(textFieldDidChange(textField:)), for: UIControlEvents.editingChanged)
secTxt.addTarget(self, action: #selector(textFieldDidChange(textField:)), for: UIControlEvents.editingChanged)
thirdTxt.addTarget(self, action: #selector(textFieldDidChange(textField:)), for: UIControlEvents.editingChanged)
fourTXt.addTarget(self, action: #selector(textFieldDidChange(textField:)), for: UIControlEvents.editingChanged)
Than create selector method
func textFieldDidChange(textField: UITextField){
let text = textField.text
if text?.utf16.count == 1{
switch textField{
case firstTxt:
secTxt.becomeFirstResponder()
case secTxt:
thirdTxt.becomeFirstResponder()
case thirdTxt:
fourTXt.becomeFirstResponder()
case fourTXt:
fourTXt.resignFirstResponder()
default:
break
}
}else{
}
}

Make an outlet collection of all your textfields and configure them like so:
OutletCollection:
#IBOutlet var textfields: [UITextField]!
Add this in viewDidLoad:
for (i, tf) in textfields.enumerated() {
tf.layer.cornerRadius = 5
tf.clipsToBounds = true
tf.tag = i
tf.delegate = self
}
In the target function, you'll restrict the count in this manner:
func textFieldDidChange(textField: UITextField) {
let text = textField.text!
if text.utf16.count >= 4 {
if textField.tag < 4 {
codeTextfields[textField.tag + 1].becomeFirstResponder()
}
else {
textField.resignFirstResponder()
}
}
}
This will the change in one of your delegate methods:
func textFieldDidBeginEditing(_ textField: UITextField) {
textField.text = ""
}
Refined version of the following answer: How to move cursor from one text field to another automatically in swift ios programmatically?

Related

How to detect if a key is pressed in iOS? [duplicate]

I am trying to check when a text field changes, equivalent too the function used for textView - textViewDidChange so far I have done this:
func textFieldDidBeginEditing(textField: UITextField) {
if self.status.text == "" && self.username.text == "" {
self.topRightButton.enabled = false
} else {
self.topRightButton.enabled = true
}
}
Which kind of works, but the topRightButton is enabled as soon as the text field is pressed on, I want it to be enabled only when text is actually typed in?
SWIFT
Swift 4.2
textfield.addTarget(self, action: #selector(ViewController.textFieldDidChange(_:)), for: .editingChanged)
and
#objc func textFieldDidChange(_ textField: UITextField) {
}
SWIFT 3 & swift 4.1
textField.addTarget(self, action: #selector(ViewController.textFieldDidChange(_:)), for: .editingChanged)
and
func textFieldDidChange(_ textField: UITextField) {
}
SWIFT 2.2
textField.addTarget(self, action: #selector(ViewController.textFieldDidChange(_:)), forControlEvents: UIControlEvents.EditingChanged)
and
func textFieldDidChange(textField: UITextField) {
//your code
}
OBJECTIVE-C
[textField addTarget:self action:#selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged];
and textFieldDidChange method is
-(void)textFieldDidChange :(UITextField *) textField{
//your code
}
You can make this connection in interface builder.
In your storyboard, click the assistant editor at the top of the screen (two circles in the middle).
Ctrl + Click on the textfield in interface builder.
Drag from EditingChanged to inside your view controller class in the assistant view.
Name your function ("textDidChange" for example) and click connect.
Swift 5.0
textField.addTarget(self, action: #selector(ViewController.textFieldDidChange(_:)),
for: .editingChanged)
and handle method:
#objc func textFieldDidChange(_ textField: UITextField) {
}
Swift 4.0
textField.addTarget(self, action: #selector(ViewController.textFieldDidChange(_:)),
for: UIControlEvents.editingChanged)
and handle method:
#objc func textFieldDidChange(_ textField: UITextField) {
}
Swift 3.0
textField.addTarget(self, action: #selector(textFieldDidChange(textField:)), for: .editingChanged)
and handle method:
func textFieldDidChange(textField: UITextField) {
}
The way I've handled it so far: in UITextFieldDelegate
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool
{
// text hasn't changed yet, you have to compute the text AFTER the edit yourself
let updatedString = (textField.text as NSString?)?.stringByReplacingCharactersInRange(range, withString: string)
// do whatever you need with this updated string (your code)
// always return true so that changes propagate
return true
}
Swift4 version
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let updatedString = (textField.text as NSString?)?.replacingCharacters(in: range, with: string)
return true
}
Swift 3
textField.addTarget(self, action: #selector(ViewController.textFieldDidChange(sender:)), for: UIControlEvents.editingChanged)
textField(_:shouldChangeCharactersIn:replacementString:) worked for me in Xcode 8, Swift 3 if you want to check every single keypress.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// Whatever code you want to run here.
// Keep in mind that the textfield hasn't yet been updated,
// so use 'string' instead of 'textField.text' if you want to
// access the string the textfield will have after a user presses a key
var statusText = self.status.text
var usernameText = self.username.text
switch textField{
case self.status:
statusText = string
case self.username:
usernameText = string
default:
break
}
if statusText == "" && usernameText == "" {
self.topRightButton.enabled = false
} else {
self.topRightButton.enabled = true
}
//Return false if you don't want the textfield to be updated
return true
}
Swift 3.0.1+ (Some of the other swift 3.0 answers are not up to date)
textField.addTarget(self, action: #selector(ViewController.textFieldDidChange(_:)),
for: UIControlEvents.editingChanged)
func textFieldDidChange(_ textField: UITextField) {
}
You can use this delegate method from UITextFieldDelegate. It fires with every character change.
(Objective C) textField:shouldChangeCharactersInRange:replacementString:
(Swift) textField(_:shouldChangeCharactersInRange:replacementString:)
However THIS ONLY FIRES BEFORE a change is made (indeed, a change is only made if you do return true from here).
Swift 4
Conform to UITextFieldDelegate.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// figure out what the new string will be after the pending edit
let updatedString = (textField.text as NSString?)?.replacingCharacters(in: range, with: string)
// Do whatever you want here
// Return true so that the change happens
return true
}
Maybe use RxSwift ?
need
pod 'RxSwift', '~> 3.0'
pod 'RxCocoa', '~> 3.0'
add imports obviously
import RxSwift
import RxCocoa
So u have a textfield : UITextField
let observable: Observable<String?> = textField.rx.text.asObservable()
observable.subscribe(
onNext: {(string: String?) in
print(string!)
})
U have other 3 methods..
onError
onCompleted
onDisposed
onNext
Swift 4
textField.addTarget(self, action: #selector(textIsChanging), for: UIControlEvents.editingChanged)
#objc func textIsChanging(_ textField:UITextField) {
print ("TextField is changing")
}
If you want to make a change once the user has typed in completely (It will be called once user dismiss keyboard or press enter).
textField.addTarget(self, action: #selector(textDidChange), for: UIControlEvents.editingDidEnd)
#objc func textDidChange(_ textField:UITextField) {
print ("TextField did changed")
}
You should follow this steps:
Make a Outlet reference to the textfield
AssignUITextFieldDelegate to the controller class
Configure yourTextField.delegate
Implement whatever function you need
Sample code:
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet var yourTextFiled : UITextField!
override func viewDidLoad() {
super.viewDidLoad()
yourTextFiled.delegate = self
}
func textFieldDidEndEditing(_ textField: UITextField) {
// your code
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
// your code
}
.
.
.
}
In case it is not possible to bind the addTarget to your UITextField, I advise you to bind one of them as suggested above, and insert the code for execution at the end of the shouldChangeCharactersIn method.
nameTextField.addTarget(self, action: #selector(RegistrationViewController.textFieldDidChange(_:)), for: .editingChanged)
#objc func textFieldDidChange(_ textField: UITextField) {
if phoneNumberTextField.text!.count == 17 && nameTextField.text!.count > 0 {
continueButtonOutlet.backgroundColor = UIColor(.green)
} else {
continueButtonOutlet.backgroundColor = .systemGray
}
}
And in call in shouldChangeCharactersIn func.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let text = textField.text else {
return true
}
let lastText = (text as NSString).replacingCharacters(in: range, with: string) as String
if phoneNumberTextField == textField {
textField.text = lastText.format("+7(NNN)-NNN-NN-NN", oldString: text)
textFieldDidChange(phoneNumberTextField)
return false
}
return true
}
There's now a UITextField delegate method available on iOS13+
optional func textFieldDidChangeSelection(_ textField: UITextField)
txf_Subject.addTarget(self, action:#selector(didChangeFirstText), for: .editingChanged)
#objc func didChangeText(textField:UITextField) {
let str = textField.text
if(str?.contains(" "))!{
let newstr = str?.replacingOccurrences(of: " ", with: "")
textField.text = newstr
}
}
#objc func didChangeFirstText(textField:UITextField) {
if(textField.text == " "){
textField.text = ""
}
}
Swift 4.2
write this in viewDidLoad
// to detect if TextField changed
TextField.addTarget(self, action: #selector(textFieldDidChange(_:)),
for: UIControl.Event.editingChanged)
write this outside viewDidLoad
#objc func textFieldDidChange(_ textField: UITextField) {
// do something
}
You could change the event by UIControl.Event.editingDidBegin or what ever you want to detect.
This is how you can add a textField text change listener using Swift 3:
Declare your class as UITextFieldDelegate
override func viewDidLoad() {
super.viewDidLoad()
textField.delegate = self
textField.addTarget(self, action: #selector(UITextFieldDelegate.textFieldShouldEndEditing(_:)), for: UIControlEvents.editingChanged)
}
Then just traditionally add a textFieldShouldEndEditing function:
func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { // do stuff
return true
}
Just in case you are interested in a SwiftUI solution, this it's working for me:
TextField("write your answer here...",
text: Binding(
get: {
return self.query
},
set: { (newValue) in
self.fetch(query: newValue) // any action you need
return self.query = newValue
}
)
)
I have to say it's not my idea, I read it in this blog: SwiftUI binding: A very simple trick
You can manage editing either by "shouldchangecharacter" delgate or "begin and end" textfield delegate. Don't forget to set delegate
I am manage to do this using "did begin and end editing" below.
//MARK:- TextViewDelegate
extension ViewController: UITextFieldDelegate {
func textFieldDidEndEditing(_ textField: UITextField) {
let count = self.tfEmail.text?.count ?? 0
if textField == self.tfEmail {
if count == 0{
//Empty textfield
}else{
//Non-Empty textfield
}
}
}
func textFieldDidBeginEditing(_ textField: UITextField) {
if textField == self.tfEmail{
//when user taps on textfield
}
}
}
swift 4
In viewDidLoad():
//ADD BUTTON TO DISMISS KEYBOARD
// Init a keyboard toolbar
let toolbar = UIView(frame: CGRect(x: 0, y: view.frame.size.height+44, width: view.frame.size.width, height: 44))
toolbar.backgroundColor = UIColor.clear
// Add done button
let doneButt = UIButton(frame: CGRect(x: toolbar.frame.size.width - 60, y: 0, width: 44, height: 44))
doneButt.setTitle("Done", for: .normal)
doneButt.setTitleColor(MAIN_COLOR, for: .normal)
doneButt.titleLabel?.font = UIFont(name: "Titillium-Semibold", size: 13)
doneButt.addTarget(self, action: #selector(dismissKeyboard), for: .touchUpInside)
toolbar.addSubview(doneButt)
USDTextField.inputAccessoryView = toolbar
Add this function:
#objc func dismissKeyboard() {
//Causes the view (or one of its embedded text fields) to resign the first responder status.
view.endEditing(true)
}

Disable button as long as several textfields are empty

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

Prevent UITextField from taking values on changing the firstResponder

I have four UITextFields, each of which represents a single digit of an OTP. I want the control to shift to consecutive textfield as the user types in the code. My implementation is below.
extension FillSignUpCodeController: UITextFieldDelegate {
func textFieldDidBeginEditing(_ textField: UITextField) {
textField.text = ""
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let inputString = (textField.text! as NSString).replacingCharacters(in: range, with: string)
if inputString.count == 1 {
switch textField {
case textFieldCodeFirstDigit:
textFieldCodeFirstDigit.text = inputString
textFieldCodeFirstDigit.resignFirstResponder()
textFieldCodeSecondDigit.becomeFirstResponder()
case textFieldCodeSecondDigit:
textFieldCodeSecondDigit.text = inputString
textFieldCodeSecondDigit.resignFirstResponder()
textFieldCodeThirdDigit.becomeFirstResponder()
case textFieldCodeThirdDigit:
textFieldCodeThirdDigit.text = inputString
textFieldCodeThirdDigit.resignFirstResponder()
textFieldCodeFourthDigit.becomeFirstResponder()
case textFieldCodeFourthDigit:
textFieldCodeFourthDigit.text = inputString
textFieldCodeFourthDigit.resignFirstResponder()
default:
return false
}
}
return true
}
}
With this piece of code, as the user types the first digit, the first textfield takes the input value and moves the control to the next textfield. However, the second text field is taking the value of the first digit. I tried setting the text to empty after changing the firstResponder but it did not work. How can I fix this issue? Thanks.
Since textField(_:shouldChangeCharactersIn:replacementString:) executes before the text is present in the text field, you should put the code inside a method that is called when the text did already change.
You should observe the changes of your text field and execute the responder-changing code there. You can find more information about that in this question.
Moreover, resigning the first responder before changing it is redundant, you don't need to do that.
It is also very redundant to handle every text field separately. I'd recommend including your text fields in an array and iterating through them.
shouldChangeCharactersInRange gets called before the textField is filled. You can do it by:
textField.addTarget(self, action: #selector(textFieldChangedValue(textField:)), for: UIControlEvents.editingChanged)
write above line for all textfields in viewDidLoad()
#objc func textFieldChangedValue(textField: UITextField) {
print(textField.text)
}
This will work
Following the answers from #the4kman and #Rahul Dasgupta, I have implemented the following:
FillUpCodeViewController.swift
override func viewDidLoad() {
setupArrrayofTextFields(textField1: textFieldCodeFirstDigit,
textField2: textFieldCodeSecondDigit,
textField3: textFieldCodeThirdDigit,
textField4: textFieldCodeFourthDigit)
}
func setupArrrayofTextFields(textField1: UITextField, textField2: UITextField,
textField3: UITextField, textField4: UITextField) {
arrayOftextFields = [textField1, textField2, textField3, textField4]
for textField in arrayOftextFields {
textField.addTarget(self, action: #selector(textFieldDidChange(textField:)), for: .editingChanged)
}
}
#objc func textFieldDidChange(textField: UITextField) {
guard textField.text?.count == 0 else {
let index: Int = arrayOftextFields.index(of: textField)!
guard index == (arrayOftextFields.count-1) else {
arrayOftextFields[index+1].becomeFirstResponder()
return
}
textField.resignFirstResponder()
return
}
}
And, again in the viewcontroller in which I have to implement the submission of recovery code, I simply inherited the FillUpCodeViewController class.
RecoveryViewController.swift
override func viewDidLoad() {
setupArrrayofTextFields(textField1: textFieldRecoveyCodeFirstDigit,
textField2: textFieldRecoveyCodeSecondDigit,
textField3: textFieldRecoveyCodeThirdDigit,
textField4: textFieldRecoveyCodeFourthDigit)
}

How to move cursor from one text field to another automatically in swift ios programmatically?

func textFieldDidBeginEditing(textField: UITextField) {
scrlView.setContentOffset(CGPointMake(0, textField.frame.origin.y-70), animated: true)
if(textField == firstDigit){
textField.becomeFirstResponder()
secondDigit.resignFirstResponder()
}
else if(textField == secondDigit){
textField.becomeFirstResponder()
thirdDigit.resignFirstResponder()
}
else if(textField == thirdDigit){
//textField.becomeFirstResponder()
fourthDigit.becomeFirstResponder()
}
I am using four textfields for OTP entry in which only one number can be entered at a time. After entering the number I need to move the cursor automatically to next textfield.
Set textField delegate and add target:
override func viewDidLoad() {
super.viewDidLoad()
first.delegate = self
second.delegate = self
third.delegate = self
fourth.delegate = self
first.addTarget(self, action: "textFieldDidChange:", forControlEvents: UIControlEvents.EditingChanged)
second.addTarget(self, action: "textFieldDidChange:", forControlEvents: UIControlEvents.EditingChanged)
third.addTarget(self, action: "textFieldDidChange:", forControlEvents: UIControlEvents.EditingChanged)
fourth.addTarget(self, action: "textFieldDidChange:", forControlEvents: UIControlEvents.EditingChanged)
}
Now when text changes change textField
func textFieldDidChange(textField: UITextField){
let text = textField.text
if text?.utf16.count >= 1{
switch textField{
case first:
second.becomeFirstResponder()
case second:
third.becomeFirstResponder()
case third:
fourth.becomeFirstResponder()
case fourth:
fourth.resignFirstResponder()
default:
break
}
}else{
}
}
And lastly when user start editing clear textField
extension ViewController: UITextFieldDelegate{
func textFieldDidBeginEditing(textField: UITextField) {
textField.text = ""
}
}
update Solution For Swift 5
In This solution, You will go to next Field. And When You Press Erase will come at previous text field.
Step 1: Set Selector for Text Field
override func viewDidLoad() {
super.viewDidLoad()
otpTextField1.addTarget(self, action: #selector(self.textFieldDidChange(textField:)), for: UIControl.Event.editingChanged)
otpTextField2.addTarget(self, action: #selector(self.textFieldDidChange(textField:)), for: UIControl.Event.editingChanged)
otpTextField3.addTarget(self, action: #selector(self.textFieldDidChange(textField:)), for: UIControl.Event.editingChanged)
otpTextField4.addTarget(self, action: #selector(self.textFieldDidChange(textField:)), for: UIControl.Event.editingChanged)
}
Step 2: Now We will handle move next text Field and Erase text Field.
#objc func textFieldDidChange(textField: UITextField){
let text = textField.text
if text?.count == 1 {
switch textField{
case otpTextField1:
otpTextField2.becomeFirstResponder()
case otpTextField2:
otpTextField3.becomeFirstResponder()
case otpTextField3:
otpTextField4.becomeFirstResponder()
case otpTextField4:
otpTextField4.resignFirstResponder()
default:
break
}
}
if text?.count == 0 {
switch textField{
case otpTextField1:
otpTextField1.becomeFirstResponder()
case otpTextField2:
otpTextField1.becomeFirstResponder()
case otpTextField3:
otpTextField2.becomeFirstResponder()
case otpTextField4:
otpTextField3.becomeFirstResponder()
default:
break
}
}
else{
}
}
Important Note: Don't Forget To set Delegate.
Swift 3 code to move the cursor from one field to another automatically in OTP(One Time Password) fields.
//Add all outlet in your code.
#IBOutlet weak var otpbox1: UITextField!
#IBOutlet weak var otpbox2: UITextField!
#IBOutlet weak var otpbox3: UITextField!
#IBOutlet weak var otpbox4: UITextField!
#IBOutlet weak var otpbox5: UITextField!
#IBOutlet weak var otpbox6: UITextField!
// Add the delegate in viewDidLoad
func viewDidLoad() {
super.viewDidLoad()
otpbox1?.delegate = self
otpbox2?.delegate = self
otpbox3?.delegate = self
otpbox4?.delegate = self
otpbox5?.delegate = self
otpbox6?.delegate = self
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range:NSRange, replacementString string: String) -> Bool {
// Range.length == 1 means,clicking backspace
if (range.length == 0){
if textField == otpbox1 {
otpbox2?.becomeFirstResponder()
}
if textField == otpbox2 {
otpbox3?.becomeFirstResponder()
}
if textField == otpbox3 {
otpbox4?.becomeFirstResponder()
}
if textField == otpbox4 {
otpbox5?.becomeFirstResponder()
}
if textField == otpbox5 {
otpbox6?.becomeFirstResponder()
}
if textField == otpbox6 {
otpbox6?.resignFirstResponder() /*After the otpbox6 is filled we capture the All the OTP textField and do the server call. If you want to capture the otpbox6 use string.*/
let otp = "\((otpbox1?.text)!)\((otpbox2?.text)!)\((otpbox3?.text)!)\((otpbox4?.text)!)\((otpbox5?.text)!)\(string)"
}
textField.text? = string
return false
}else if (range.length == 1) {
if textField == otpbox6 {
otpbox5?.becomeFirstResponder()
}
if textField == otpbox5 {
otpbox4?.becomeFirstResponder()
}
if textField == otpbox4 {
otpbox3?.becomeFirstResponder()
}
if textField == otpbox3 {
otpbox2?.becomeFirstResponder()
}
if textField == otpbox2 {
otpbox1?.becomeFirstResponder()
}
if textField == otpbox1 {
otpbox1?.resignFirstResponder()
}
textField.text? = ""
return false
}
return true
}
This is similar to how UberEats has their otp fields. You can just copy and paste this into a file and run it to see how it works. But don't forget to add the MyTextField class or it won't work.
If you want it to automatically to move to the next textfield after a number is entered and still be able to move backwards if the back button is pressed WHILE the textField is empty this will help you.
Like the very first thing I said, this is similar to how UberEats has their sms textFields working. You can't just randomly press a textField and select it. Using this you can only move forward and backwards. The ux is subjective but if Uber uses it the ux must be valid. I say it's similar because they also have a gray box covering the textField so I'm not sure what's going on behind it. This was the closest I could get.
First your going to have to subclass UITextField using this answer to detect when the backspace button is pressed. When the back button is pressed your going to erase everything inside that field AND the previous field then jump to the previous field.
Second your going to have to prevent the user from being able to select the left side of the cursor once a char is inside the textField using this answer. You override the method in the same subClass from the first step.
Third you need to detect which textField is currently active using this answer
Fourth your going to have to run some checks inside func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool using this YouTube tutorial. I added some things to his work.
I'm doing everything programmatically so you can copy and paste the entire code into a project and run it
First create a subClass of UITextField and name it MyTextField:
protocol MyTextFieldDelegate: class {
func textFieldDidDelete()
}
// 1. subclass UITextField and create protocol for it to know when the backButton is pressed
class MyTextField: UITextField {
weak var myDelegate: MyTextFieldDelegate? // make sure to declare this as weak to prevent a memory leak/retain cycle
override func deleteBackward() {
super.deleteBackward()
myDelegate?.textFieldDidDelete()
}
// when a char is inside the textField this keeps the cursor to the right of it. If the user can get on the left side of the char and press the backspace the current char won't get deleted
override func closestPosition(to point: CGPoint) -> UITextPosition? {
let beginning = self.beginningOfDocument
let end = self.position(from: beginning, offset: self.text?.count ?? 0)
return end
}
}
Second inside the class with the OTP textfields, set the class to use the UITextFieldDelegate and the MyTextFieldDelegate, then create a class property and name it activeTextField. When whichever textField becomes active inside textFieldDidBeginEditing you set the activeTextField to that. In viewDidLoad set all the textFields to use both delegates.
Make sure the First otpTextField is ENABLED and the second, third, and fourth otpTextFields are ALL initially DIASABLED
import UIKit
// 2. set the class to BOTH Delegates
class ViewController: UIViewController, UITextFieldDelegate, MyTextFieldDelegate {
let staticLabel: UILabel = {
let label = UILabel()
label.translatesAutoresizingMaskIntoConstraints = false
label.font = UIFont.systemFont(ofSize: 17)
label.text = "Enter the SMS code sent to your phone"
return label
}()
// 3. make each textField of type MYTextField
let otpTextField1: MyTextField = {
let textField = MyTextField()
textField.translatesAutoresizingMaskIntoConstraints = false
textField.font = UIFont.systemFont(ofSize: 25)
textField.autocorrectionType = .no
textField.keyboardType = .numberPad
textField.textAlignment = .center
// **important this is initially ENABLED
return textField
}()
let otpTextField2: MyTextField = {
let textField = MyTextField()
textField.translatesAutoresizingMaskIntoConstraints = false
textField.font = UIFont.systemFont(ofSize: 25)
textField.autocorrectionType = .no
textField.keyboardType = .numberPad
textField.textAlignment = .center
textField.isEnabled = false // **important this is initially DISABLED
return textField
}()
let otpTextField3: MyTextField = {
let textField = MyTextField()
textField.translatesAutoresizingMaskIntoConstraints = false
textField.font = UIFont.systemFont(ofSize: 25)
textField.autocorrectionType = .no
textField.keyboardType = .numberPad
textField.textAlignment = .center
textField.isEnabled = false // **important this is initially DISABLED
return textField
}()
let otpTextField4: MyTextField = {
let textField = MyTextField()
textField.translatesAutoresizingMaskIntoConstraints = false
textField.font = UIFont.systemFont(ofSize: 25)
textField.autocorrectionType = .no
textField.keyboardType = .numberPad
textField.textAlignment = .center
textField.isEnabled = false // **important this is initially DISABLED
return textField
}()
// 4. create this property to know which textField is active. Set it in step 8 and use it in step 9
var activeTextField = UITextField()
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
// 5. set the regular UItextField delegate to each textField
otpTextField1.delegate = self
otpTextField2.delegate = self
otpTextField3.delegate = self
otpTextField4.delegate = self
// 6. set the subClassed textField delegate to each textField
otpTextField1.myDelegate = self
otpTextField2.myDelegate = self
otpTextField3.myDelegate = self
otpTextField4.myDelegate = self
configureAnchors()
// 7. once the screen appears show the keyboard
otpTextField1.becomeFirstResponder()
}
// 8. when a textField is active set the activeTextField property to that textField
func textFieldDidBeginEditing(_ textField: UITextField) {
activeTextField = textField
}
// 9. when the backButton is pressed, the MyTextField delegate will get called. The activeTextField will let you know which textField the backButton was pressed in. Depending on the textField certain textFields will become enabled and disabled.
func textFieldDidDelete() {
if activeTextField == otpTextField1 {
print("backButton was pressed in otpTextField1")
// do nothing
}
if activeTextField == otpTextField2 {
print("backButton was pressed in otpTextField2")
otpTextField2.isEnabled = false
otpTextField1.isEnabled = true
otpTextField1.becomeFirstResponder()
otpTextField1.text = ""
}
if activeTextField == otpTextField3 {
print("backButton was pressed in otpTextField3")
otpTextField3.isEnabled = false
otpTextField2.isEnabled = true
otpTextField2.becomeFirstResponder()
otpTextField2.text = ""
}
if activeTextField == otpTextField4 {
print("backButton was pressed in otpTextField4")
otpTextField4.isEnabled = false
otpTextField3.isEnabled = true
otpTextField3.becomeFirstResponder()
otpTextField3.text = ""
}
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if let text = textField.text {
// 10. when the user enters something in the first textField it will automatically adjust to the next textField and in the process do some disabling and enabling. This will proceed until the last textField
if (text.count < 1) && (string.count > 0) {
if textField == otpTextField1 {
otpTextField1.isEnabled = false
otpTextField2.isEnabled = true
otpTextField2.becomeFirstResponder()
}
if textField == otpTextField2 {
otpTextField2.isEnabled = false
otpTextField3.isEnabled = true
otpTextField3.becomeFirstResponder()
}
if textField == otpTextField3 {
otpTextField3.isEnabled = false
otpTextField4.isEnabled = true
otpTextField4.becomeFirstResponder()
}
if textField == otpTextField4 {
// do nothing or better yet do something now that you have all four digits for the sms code. Once the user lands on this textField then the sms code is complete
}
textField.text = string
return false
} // 11. if the user gets to the last textField and presses the back button everything above will get reversed
else if (text.count >= 1) && (string.count == 0) {
if textField == otpTextField2 {
otpTextField2.isEnabled = false
otpTextField1.isEnabled = true
otpTextField1.becomeFirstResponder()
otpTextField1.text = ""
}
if textField == otpTextField3 {
otpTextField3.isEnabled = false
otpTextField2.isEnabled = true
otpTextField2.becomeFirstResponder()
otpTextField2.text = ""
}
if textField == otpTextField4 {
otpTextField4.isEnabled = false
otpTextField3.isEnabled = true
otpTextField3.becomeFirstResponder()
otpTextField3.text = ""
}
if textField == otpTextField1 {
// do nothing
}
textField.text = ""
return false
} // 12. after pressing the backButton and moving forward again you will have to do what's in step 10 all over again
else if text.count >= 1 {
if textField == otpTextField1 {
otpTextField1.isEnabled = false
otpTextField2.isEnabled = true
otpTextField2.becomeFirstResponder()
}
if textField == otpTextField2 {
otpTextField2.isEnabled = false
otpTextField3.isEnabled = true
otpTextField3.becomeFirstResponder()
}
if textField == otpTextField3 {
otpTextField3.isEnabled = false
otpTextField4.isEnabled = true
otpTextField4.becomeFirstResponder()
}
if textField == otpTextField4 {
// do nothing or better yet do something now that you have all four digits for the sms code. Once the user lands on this textField then the sms code is complete
}
textField.text = string
return false
}
}
return true
}
//**Optional** For a quick setup use this below. Here is how to add a gray line to the textFields and here are the anchors:
// if your app supports portrait and horizontal your going to have to make some adjustments to this every time the phone rotates
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
addBottomLayerTo(textField: otpTextField1)
addBottomLayerTo(textField: otpTextField2)
addBottomLayerTo(textField: otpTextField3)
addBottomLayerTo(textField: otpTextField4)
}
// this adds a lightGray line at the bottom of the textField
func addBottomLayerTo(textField: UITextField) {
let layer = CALayer()
layer.backgroundColor = UIColor.lightGray.cgColor
layer.frame = CGRect(x: 0, y: textField.frame.height - 2, width: textField.frame.width, height: 2)
textField.layer.addSublayer(layer)
}
func configureAnchors() {
view.addSubview(staticLabel)
view.addSubview(otpTextField1)
view.addSubview(otpTextField2)
view.addSubview(otpTextField3)
view.addSubview(otpTextField4)
let width = view.frame.width / 5
staticLabel.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor, constant: 15).isActive = true
staticLabel.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10).isActive = true
staticLabel.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -10).isActive = true
// textField 1
otpTextField1.topAnchor.constraint(equalTo: staticLabel.bottomAnchor, constant: 10).isActive = true
otpTextField1.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor, constant: 10).isActive = true
otpTextField1.widthAnchor.constraint(equalToConstant: width).isActive = true
otpTextField1.heightAnchor.constraint(equalToConstant: width).isActive = true
// textField 2
otpTextField2.topAnchor.constraint(equalTo: staticLabel.bottomAnchor, constant: 10).isActive = true
otpTextField2.leadingAnchor.constraint(equalTo: otpTextField1.trailingAnchor, constant: 10).isActive = true
otpTextField2.widthAnchor.constraint(equalTo: otpTextField1.widthAnchor).isActive = true
otpTextField2.heightAnchor.constraint(equalToConstant: width).isActive = true
// textField 3
otpTextField3.topAnchor.constraint(equalTo: staticLabel.bottomAnchor, constant: 10).isActive = true
otpTextField3.leadingAnchor.constraint(equalTo: otpTextField2.trailingAnchor, constant: 10).isActive = true
otpTextField3.widthAnchor.constraint(equalTo: otpTextField1.widthAnchor).isActive = true
otpTextField3.heightAnchor.constraint(equalToConstant: width).isActive = true
// textField 4
otpTextField4.topAnchor.constraint(equalTo: staticLabel.bottomAnchor, constant: 10).isActive = true
otpTextField4.leadingAnchor.constraint(equalTo: otpTextField3.trailingAnchor, constant: 10).isActive = true
otpTextField4.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor, constant: -10).isActive = true
otpTextField4.widthAnchor.constraint(equalTo: otpTextField1.widthAnchor).isActive = true
otpTextField4.heightAnchor.constraint(equalToConstant: width).isActive = true
}
}
This is separate from the answer above but if you need to add multiple characters to each otpTextField then follow this answer.
Firstly we'll need to set the tag for the UITextField;
func textFieldShouldReturnSingle(_ textField: UITextField , newString : String)
{
let nextTag: Int = textField.tag + 1
let nextResponder: UIResponder? = textField.superview?.superview?.viewWithTag(nextTag)
textField.text = newString
if let nextR = nextResponder
{
// Found next responder, so set it.
nextR.becomeFirstResponder()
}
else
{
// Not found, so remove keyboard.
textField.resignFirstResponder()
}
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let newString = ((textField.text)! as NSString).replacingCharacters(in: range, with: string)
let newLength = newString.characters.count
if newLength == 1 {
textFieldShouldReturnSingle(textField , newString : newString)
return false
}
return true
}
Note: The UITextField takes only one character in number format, which is in OTP format.
Objective c and Swift 4.2 to move the cursor from one field to another automatically in OTP(One Time Password) fields
Here i am taking one view controller
]1
Then give the Tag values for each TextFiled.Those related reference images are shown below
Enter tag value for first textfiled --> 1,2ndTextfiled ---->2,3rd TextFiled --->3 4rth TextFiled---->4
Then assign Textfiled Delegates and write below code and see the magic
- (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)){
// 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;
}
swift4.2 version code
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textField.text!.count < 1 && string.count > 0 {
let tag = textField.tag + 1;
let nextResponder = textField.superview?.viewWithTag(tag)
if (nextResponder != nil){
textField.resignFirstResponder()
}
textField.text = string;
if (nextResponder != nil){
nextResponder?.becomeFirstResponder()
}
return false;
}else if (textField.text?.count)! >= 1 && string.count == 0 {
let prevTag = textField.tag - 1
let prevResponser = textField.superview?.viewWithTag(prevTag)
if (prevResponser != nil){
textField.resignFirstResponder()
}
textField.text = string
if (prevResponser != nil){
prevResponser?.becomeFirstResponder()
}
return false
}
return true;
}
Here I was take 4 TextField
#IBOutlet var txtOtp: [BottomBorderTextField]!
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
defer{
if !string.isEmpty {
textField.text = string
textField.resignFirstResponder()
if let index = self.txtOtp.index(where:{$0 === textField}) {
if index < 3 {
self.txtOtp[index + 1].becomeFirstResponder()
}
}
}
}
return true
}
**call from UITextfieldDelegate function and make next text field the first responder and no need to add target and remember to set delegates of all text fields in viewDidLoad **
extension ViewController : UITextFieldDelegate {
func textFieldShouldReturn(textField: UITextField) -> Bool {
nextTextFieldToFirstResponder(textField)
return true;
}
func nextTextFieldToFirstResponder(textField: UITextField) {
if textField == emailTextField
{
self.firstNameTextField.becomeFirstResponder()
}
else if textField == firstNameTextField {
self.lastNameTextField.becomeFirstResponder()
}
else if textField == lastNameTextField {
self.passwordTextField.becomeFirstResponder()
}
else if textField == passwordTextField {
self.confirmPassTextField.becomeFirstResponder()
}
else if textField == confirmPassTextField {
self.confirmPassTextField.resignFirstResponder()
}
}
I have tried many codes and finally this worked for me in Swift 3.0 Latest [March 2017]
The "ViewController" class should inherited the "UITextFieldDelegate" for making this code working.
class ViewController: UIViewController,UITextFieldDelegate
Add the Text field with the Proper Tag nuber and this tag number is used to take the control to appropriate text field based on incremental tag number assigned to it.
override func viewDidLoad() {
userNameTextField.delegate = self
userNameTextField.tag = 0
userNameTextField.returnKeyType = UIReturnKeyType.next
passwordTextField.delegate = self
passwordTextField.tag = 1
passwordTextField.returnKeyType = UIReturnKeyType.go
}
In the above code, the "returnKeyType = UIReturnKeyType.next" where will make the Key pad return key to display as "Next" you also have other options as "Join/Go" etc, based on your application change the values.
This "textFieldShouldReturn" is a method of UITextFieldDelegate controlled and here we have next field selection based on the Tag value incrementation
func textFieldShouldReturn(_ textField: UITextField) -> Bool
{
if let nextField = textField.superview?.viewWithTag(textField.tag + 1) as? UITextField {
nextField.becomeFirstResponder()
} else {
textField.resignFirstResponder()
return true;
}
return false
}
Use textFieldShouldBeginEditing method
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
scrlView.setContentOffset(CGPointMake(0, textField.frame.origin.y-70),
animated:true)
if(textField == firstDigit){
textField.becomeFirstResponder()
secondDigit.resignFirstResponder()
}
else if(textField == secondDigit){
textField.becomeFirstResponder()
thirdDigit.resignFirstResponder()
}
else if(textField == thirdDigit){
//textField.becomeFirstResponder()
fourthDigit.becomeFirstResponder()
}
return true;
}
//MARK:- IBOutlets
#IBOutlet weak var tfFirstDigit: UITextField!
#IBOutlet weak var tfSecondDigit: UITextField!
#IBOutlet weak var tfThirdDigit: UITextField!
#IBOutlet weak var tfFourthDigit: UITextField!
//MARK:- view Life Cycle
override func viewDidLoad() {
super.viewDidLoad()
tfFirstDigit.addTarget(self, action: #selector(self.textFieldDidChange(textField:)), for: UIControl.Event.editingChanged)
tfSecondDigit.addTarget(self, action: #selector(self.textFieldDidChange(textField:)), for: UIControl.Event.editingChanged)
tfThirdDigit.addTarget(self, action: #selector(self.textFieldDidChange(textField:)), for: UIControl.Event.editingChanged)
tfFourthDigit.addTarget(self, action: #selector(self.textFieldDidChange(textField:)), for: UIControl.Event.editingChanged)
}
//MARK:- Text Field Delegate methods
#objc func textFieldDidChange(textField: UITextField){
let text = textField.text
if (text?.utf16.count)! >= 1{
switch textField{
case tfFirstDigit:
tfSecondDigit.becomeFirstResponder()
case tfSecondDigit:
tfThirdDigit.becomeFirstResponder()
case tfThirdDigit:
tfFourthDigit.becomeFirstResponder()
case tfFourthDigit:
tfFourthDigit.resignFirstResponder()
default:
break
}
}else{
}
}
func textFieldDidBeginEditing(_ textField: UITextField) {
textField.text = ""
}
let do something different using IQKeyboardManager.It work like charm. Do not forget set delegate for every text field.
//MARK:- TextField delegate methods
#objc func textFieldDidChange(textField: UITextField){
if textField.text!.count == 1{
if IQKeyboardManager.shared().canGoNext{
IQKeyboardManager.shared().goNext()
}
}else{
if IQKeyboardManager.shared().canGoPrevious{
IQKeyboardManager.shared().goPrevious()
}
}
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if string == " "{
return false
}else if string.isEmpty{
return true
}else if textField.text!.count == 1{
textField.text = string
if IQKeyboardManager.shared().canGoNext{
IQKeyboardManager.shared().goNext()
}
return false
}
return true
}
for swift 3
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;
}

How do I check when a UITextField changes?

I am trying to check when a text field changes, equivalent too the function used for textView - textViewDidChange so far I have done this:
func textFieldDidBeginEditing(textField: UITextField) {
if self.status.text == "" && self.username.text == "" {
self.topRightButton.enabled = false
} else {
self.topRightButton.enabled = true
}
}
Which kind of works, but the topRightButton is enabled as soon as the text field is pressed on, I want it to be enabled only when text is actually typed in?
SWIFT
Swift 4.2
textfield.addTarget(self, action: #selector(ViewController.textFieldDidChange(_:)), for: .editingChanged)
and
#objc func textFieldDidChange(_ textField: UITextField) {
}
SWIFT 3 & swift 4.1
textField.addTarget(self, action: #selector(ViewController.textFieldDidChange(_:)), for: .editingChanged)
and
func textFieldDidChange(_ textField: UITextField) {
}
SWIFT 2.2
textField.addTarget(self, action: #selector(ViewController.textFieldDidChange(_:)), forControlEvents: UIControlEvents.EditingChanged)
and
func textFieldDidChange(textField: UITextField) {
//your code
}
OBJECTIVE-C
[textField addTarget:self action:#selector(textFieldDidChange:) forControlEvents:UIControlEventEditingChanged];
and textFieldDidChange method is
-(void)textFieldDidChange :(UITextField *) textField{
//your code
}
You can make this connection in interface builder.
In your storyboard, click the assistant editor at the top of the screen (two circles in the middle).
Ctrl + Click on the textfield in interface builder.
Drag from EditingChanged to inside your view controller class in the assistant view.
Name your function ("textDidChange" for example) and click connect.
Swift 5.0
textField.addTarget(self, action: #selector(ViewController.textFieldDidChange(_:)),
for: .editingChanged)
and handle method:
#objc func textFieldDidChange(_ textField: UITextField) {
}
Swift 4.0
textField.addTarget(self, action: #selector(ViewController.textFieldDidChange(_:)),
for: UIControlEvents.editingChanged)
and handle method:
#objc func textFieldDidChange(_ textField: UITextField) {
}
Swift 3.0
textField.addTarget(self, action: #selector(textFieldDidChange(textField:)), for: .editingChanged)
and handle method:
func textFieldDidChange(textField: UITextField) {
}
The way I've handled it so far: in UITextFieldDelegate
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool
{
// text hasn't changed yet, you have to compute the text AFTER the edit yourself
let updatedString = (textField.text as NSString?)?.stringByReplacingCharactersInRange(range, withString: string)
// do whatever you need with this updated string (your code)
// always return true so that changes propagate
return true
}
Swift4 version
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let updatedString = (textField.text as NSString?)?.replacingCharacters(in: range, with: string)
return true
}
Swift 3
textField.addTarget(self, action: #selector(ViewController.textFieldDidChange(sender:)), for: UIControlEvents.editingChanged)
textField(_:shouldChangeCharactersIn:replacementString:) worked for me in Xcode 8, Swift 3 if you want to check every single keypress.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// Whatever code you want to run here.
// Keep in mind that the textfield hasn't yet been updated,
// so use 'string' instead of 'textField.text' if you want to
// access the string the textfield will have after a user presses a key
var statusText = self.status.text
var usernameText = self.username.text
switch textField{
case self.status:
statusText = string
case self.username:
usernameText = string
default:
break
}
if statusText == "" && usernameText == "" {
self.topRightButton.enabled = false
} else {
self.topRightButton.enabled = true
}
//Return false if you don't want the textfield to be updated
return true
}
Swift 3.0.1+ (Some of the other swift 3.0 answers are not up to date)
textField.addTarget(self, action: #selector(ViewController.textFieldDidChange(_:)),
for: UIControlEvents.editingChanged)
func textFieldDidChange(_ textField: UITextField) {
}
You can use this delegate method from UITextFieldDelegate. It fires with every character change.
(Objective C) textField:shouldChangeCharactersInRange:replacementString:
(Swift) textField(_:shouldChangeCharactersInRange:replacementString:)
However THIS ONLY FIRES BEFORE a change is made (indeed, a change is only made if you do return true from here).
Swift 4
Conform to UITextFieldDelegate.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// figure out what the new string will be after the pending edit
let updatedString = (textField.text as NSString?)?.replacingCharacters(in: range, with: string)
// Do whatever you want here
// Return true so that the change happens
return true
}
Maybe use RxSwift ?
need
pod 'RxSwift', '~> 3.0'
pod 'RxCocoa', '~> 3.0'
add imports obviously
import RxSwift
import RxCocoa
So u have a textfield : UITextField
let observable: Observable<String?> = textField.rx.text.asObservable()
observable.subscribe(
onNext: {(string: String?) in
print(string!)
})
U have other 3 methods..
onError
onCompleted
onDisposed
onNext
Swift 4
textField.addTarget(self, action: #selector(textIsChanging), for: UIControlEvents.editingChanged)
#objc func textIsChanging(_ textField:UITextField) {
print ("TextField is changing")
}
If you want to make a change once the user has typed in completely (It will be called once user dismiss keyboard or press enter).
textField.addTarget(self, action: #selector(textDidChange), for: UIControlEvents.editingDidEnd)
#objc func textDidChange(_ textField:UITextField) {
print ("TextField did changed")
}
You should follow this steps:
Make a Outlet reference to the textfield
AssignUITextFieldDelegate to the controller class
Configure yourTextField.delegate
Implement whatever function you need
Sample code:
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet var yourTextFiled : UITextField!
override func viewDidLoad() {
super.viewDidLoad()
yourTextFiled.delegate = self
}
func textFieldDidEndEditing(_ textField: UITextField) {
// your code
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
// your code
}
.
.
.
}
In case it is not possible to bind the addTarget to your UITextField, I advise you to bind one of them as suggested above, and insert the code for execution at the end of the shouldChangeCharactersIn method.
nameTextField.addTarget(self, action: #selector(RegistrationViewController.textFieldDidChange(_:)), for: .editingChanged)
#objc func textFieldDidChange(_ textField: UITextField) {
if phoneNumberTextField.text!.count == 17 && nameTextField.text!.count > 0 {
continueButtonOutlet.backgroundColor = UIColor(.green)
} else {
continueButtonOutlet.backgroundColor = .systemGray
}
}
And in call in shouldChangeCharactersIn func.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let text = textField.text else {
return true
}
let lastText = (text as NSString).replacingCharacters(in: range, with: string) as String
if phoneNumberTextField == textField {
textField.text = lastText.format("+7(NNN)-NNN-NN-NN", oldString: text)
textFieldDidChange(phoneNumberTextField)
return false
}
return true
}
There's now a UITextField delegate method available on iOS13+
optional func textFieldDidChangeSelection(_ textField: UITextField)
txf_Subject.addTarget(self, action:#selector(didChangeFirstText), for: .editingChanged)
#objc func didChangeText(textField:UITextField) {
let str = textField.text
if(str?.contains(" "))!{
let newstr = str?.replacingOccurrences(of: " ", with: "")
textField.text = newstr
}
}
#objc func didChangeFirstText(textField:UITextField) {
if(textField.text == " "){
textField.text = ""
}
}
Swift 4.2
write this in viewDidLoad
// to detect if TextField changed
TextField.addTarget(self, action: #selector(textFieldDidChange(_:)),
for: UIControl.Event.editingChanged)
write this outside viewDidLoad
#objc func textFieldDidChange(_ textField: UITextField) {
// do something
}
You could change the event by UIControl.Event.editingDidBegin or what ever you want to detect.
This is how you can add a textField text change listener using Swift 3:
Declare your class as UITextFieldDelegate
override func viewDidLoad() {
super.viewDidLoad()
textField.delegate = self
textField.addTarget(self, action: #selector(UITextFieldDelegate.textFieldShouldEndEditing(_:)), for: UIControlEvents.editingChanged)
}
Then just traditionally add a textFieldShouldEndEditing function:
func textFieldShouldEndEditing(_ textField: UITextField) -> Bool { // do stuff
return true
}
Just in case you are interested in a SwiftUI solution, this it's working for me:
TextField("write your answer here...",
text: Binding(
get: {
return self.query
},
set: { (newValue) in
self.fetch(query: newValue) // any action you need
return self.query = newValue
}
)
)
I have to say it's not my idea, I read it in this blog: SwiftUI binding: A very simple trick
You can manage editing either by "shouldchangecharacter" delgate or "begin and end" textfield delegate. Don't forget to set delegate
I am manage to do this using "did begin and end editing" below.
//MARK:- TextViewDelegate
extension ViewController: UITextFieldDelegate {
func textFieldDidEndEditing(_ textField: UITextField) {
let count = self.tfEmail.text?.count ?? 0
if textField == self.tfEmail {
if count == 0{
//Empty textfield
}else{
//Non-Empty textfield
}
}
}
func textFieldDidBeginEditing(_ textField: UITextField) {
if textField == self.tfEmail{
//when user taps on textfield
}
}
}
swift 4
In viewDidLoad():
//ADD BUTTON TO DISMISS KEYBOARD
// Init a keyboard toolbar
let toolbar = UIView(frame: CGRect(x: 0, y: view.frame.size.height+44, width: view.frame.size.width, height: 44))
toolbar.backgroundColor = UIColor.clear
// Add done button
let doneButt = UIButton(frame: CGRect(x: toolbar.frame.size.width - 60, y: 0, width: 44, height: 44))
doneButt.setTitle("Done", for: .normal)
doneButt.setTitleColor(MAIN_COLOR, for: .normal)
doneButt.titleLabel?.font = UIFont(name: "Titillium-Semibold", size: 13)
doneButt.addTarget(self, action: #selector(dismissKeyboard), for: .touchUpInside)
toolbar.addSubview(doneButt)
USDTextField.inputAccessoryView = toolbar
Add this function:
#objc func dismissKeyboard() {
//Causes the view (or one of its embedded text fields) to resign the first responder status.
view.endEditing(true)
}

Resources