backspace press not calling uitextfield's method - ios

I have four textField for purpose of OTP, and also set delegate to my viewcontroller in ViewDidLoad() method
I have also implement delegate method:
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
if string.characters.count > 1 {
return false
} else {
var tag = textField.tag
if string.characters.count == 0 {
tag -= 1
} else {
tag += 1
}
textField.text = string
// Try to find next responder
let nextResponder = textField.superview?.viewWithTag(tag) as UIResponder!
nextResponder?.becomeFirstResponder()
}
return false
}
func textFieldShouldReturn(textField: UITextField) -> Bool {
print("BACKSPACE PRESSED")
return true
}
But both of this method not call when field is empty, actually i want clear text. Any Idea how to detect "backspace" is pressed when textfield is empty

There is code to detect backspace
const char * _char = [string cStringUsingEncoding:NSUTF8StringEncoding];
int isBackSpace = strcmp(_char, "\b");
if (isBackSpace == -8) {
// Code there
}

Directly Identify backspace is not proper logic. As per your requirement your text field become Blank then you want to previous text field become first responder then you need to write your code like this way
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
var txtAfterUpdate:NSString = textField.text! as NSString
txtAfterUpdate = txtAfterUpdate.replacingCharacters(in: range, with: string) as NSString
let updatedString = txtAfterUpdate as String
if updatedString.characters.count == 0 {
var tag = textField.tag
tag -= 1
let nextResponder = textField.superview?.viewWithTag(tag) as UIResponder!
nextResponder?.becomeFirstResponder()
return true
} else{
// rest of your functionality
}
return true
}
I hope it will help you.

I get solution by overriding Following Method of UITextField.
class MYTextField: UITextField {
override func deleteBackward() {
super.deleteBackward()
delegate?.textFieldShouldReturn!(self)
print("_____________BACKSPACE_PRESSED_____________")
}
}

Related

How to achieve this UITextField layout/design used for SMS Verification Code?

I am trying to achieve the following functionality for SMS Verification code. How would I achieve it? After looking into a bunch of libraries in Github, none was similar enough to this design, which is widely used in the industry.
I tried implementing it with six textfields but found a number of problems as a lot of work, blocking all but the first textfield for initial input, laggy move of firstResponder and whatnot, what made me wonder if having 6 textfields is really the best approach.
Rounding the borders is piece of cake. The hard part is the functionality (i.e the cursor moving smoothly, getting back and forth, making all of them red when input is wrong, etc)
What do you guys think? How could I achieve such behavior/functionality?
Try Below Code. (Currently it contains 4 UITextField, change it as per your requirement.)
extension ConfirmationCodeViewController: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if !(string == "") {
textField.text = string
if textField == txtFirst {
txtSecond.becomeFirstResponder()
}
else if textField == txtSecond {
txtThird.becomeFirstResponder()
}
else if textField == txtThird {
txtForth.becomeFirstResponder()
}
else {
textField.resignFirstResponder()
}
return false
}
return true
}
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
if (textField.text?.count ?? 0) > 0 {
}
return true
}
}
Try this (make sure textField tags are added and the delegate is connected)
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if (string != "") {
if (textField.text == "") {
textField.text = string
let nextResponder: UIResponder? = view.viewWithTag(textField.tag + 1)
if (nextResponder != nil) {
nextResponder?.becomeFirstResponder()
}
}
return false
} else {
textField.text = string
let nextResponder: UIResponder? = view.viewWithTag(textField.tag - 1)
if (nextResponder != nil) {
nextResponder?.becomeFirstResponder()
}
return false
}
}
Please have a look to this git hub library...
PinCodeTextField
Your can also customise the number of input textfields.
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
var nextResponder = textField.superview?.viewWithTag(nextTag)
if (nextResponder == nil){
nextResponder = textField.superview?.viewWithTag(1)
}
textField.text = string;
//resign at 4th textfield
if textField.tag == 4{
txt4.resignFirstResponder()
}else{
nextResponder?.becomeFirstResponder()
}
return false
}
}
Try above code, I have taken 4 UITextFields and resign it to last textfield and you can take as much you want it.

How to limit length of input of Textfield - Swift 2

This code works perfectly, and I can't key in anything other than integers, even when I try to paste it in.
I'd like to add one more refinement, which is to limit the length of the input. Here's my code:
func initializeTextFields()
{
APTeams.delegate = self
APTeams.keyboardType = UIKeyboardType.NumberPad
APRounds.delegate = self
APRounds.keyboardType = UIKeyboardType.NumberPad
APBreakers.delegate = self
APBreakers.keyboardType = UIKeyboardType.NumberPad
}
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
// Find out what the text field will be after adding the current edit
let text = (textField.text! as NSString).stringByReplacingCharactersInRange(range, withString: string)
if text == "" {
return true
}
if let _ = Int(text) {
return true
}
else {
return false
}
}
What do I have to add to it to achieve this? The maximum input length for all the TextFields should be <= 4.
BTW, all code is in Swift 2. From problems I faced when trying to implement answers to questions I've asked before, I gather that some of the methods are different.
count(textField.text) is deprecated in SWIFT 2.0
public func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
if let textField = textField as? UITextField {
if (range.length + range.location > textField.text!.characters.count) {
return false;
}
let newLength = textField.text!.characters.count + string.characters.count - range.length;
switch(textField.tag) { //In case you want to handle multiple textfields
case Constants.TAG1:
return newLength <= 20;
case Constants.TAG2:
return newLength <= 30;
default:
return newLength <= 15;
}
}
return true;
}
Write the condition in textfield delegate method as:-
func textField(textField: UITextField!, shouldChangeCharactersInRange range: NSRange, replacementString string: String!) -> Bool {
if (count(textField.text) > 4 && range.length == 0)
{
return false // return NO to not change text
}
else
{
}
write all your code part in else part.
The delegate methods or an NSFormatter such as NSNumberFormatter.
The formatter is the most appropriate generally as it also provides localization support.
I know its bit too late but still I want share it too, I found a way which is much easier to set a limit character for an textfield in swift development.
Here is the code:-
import UIKit
private var maxLengths = [UITextField: Int]()
extension UITextField {
#IBInspectable var maxLength: Int {
get {
guard let length = maxLengths[self] else {
return Int.max
}
return length
}
set {
maxLengths[self] = newValue
addTarget(self, action: #selector(limitLength), for: .editingChanged)
}
}
#objc func limitLength(textField: UITextField) {
guard let prospectiveText = textField.text, prospectiveText.count > maxLength else {
return
}
let selection = selectedTextRange
let maxCharIndex = prospectiveText.index(prospectiveText.startIndex, offsetBy: maxLength)
#if swift(>=4.0)
text = String(prospectiveText[..<maxCharIndex])
#else
text = prospectiveText.substring(to: maxCharIndex)
#endif
selectedTextRange = selection
}
}
and just set the limit through the panel.
Image:
Just try this to limit the length of TF
Editing changed Action Outlet of TF
#IBAction func otpTF2EditingChnaged(_ sender: UITextField) {
if (sender.text?.count == 1) {
otpTF3.becomeFirstResponder()
}
checkMaxLength(textField: sender , maxLength: 1)
}
Function That will limit the length
private func checkMaxLength(textField: UITextField!, maxLength: Int) {
if (textField.text!.count > maxLength) {
textField.deleteBackward()
}
}

Set the maximum character length of a UITextField in Swift

I know there are other topics on this, but I can't seem to find out how to implement it.
I'm trying to limit a UITextField to only five characters.
Preferably alphanumeric, -, ., and _.
I've seen this code:
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange,
replacementString string: String) -> Bool
{
let maxLength = 4
let currentString: NSString = textField.text
let newString: NSString =
currentString.stringByReplacingCharactersInRange(range, withString: string)
return newString.length <= maxLength
}
and
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let length = count(textField.text.utf16) + count(string.utf16) - range.length
return length <= 10
}
How can I actually implement it? Which "textfield" should I swap out for my custom named UITextField?
Your view controller should conform to UITextFieldDelegate, like below:
class MyViewController: UIViewController, UITextFieldDelegate {
}
Set the delegate of your textfield: myTextField.delegate = self
Implement the method in your view controller:
textField(_:shouldChangeCharactersInRange:replacementString:)
All together:
class MyViewController: UIViewController, UITextFieldDelegate // Set delegate to class
#IBOutlet var mytextField: UITextField // textfield variable
override func viewDidLoad() {
super.viewDidLoad()
mytextField.delegate = self // set delegate
}
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange,
replacementString string: String) -> Bool
{
let maxLength = 4
let currentString: NSString = textField.text
let newString: NSString = currentString.stringByReplacingCharactersInRange(range, withString: string)
return newString.length <= maxLength
}
For Swift 4
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let maxLength = 1
let currentString: NSString = (textField.text ?? "") as NSString
let newString: NSString = currentString.replacingCharacters(in: range, with: string) as NSString
return newString.length <= maxLength
}
For Swift 5
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let maxLength = 1
let currentString = (textField.text ?? "") as NSString
let newString = currentString.replacingCharacters(in: range, with: string)
return newString.count <= maxLength
}
Allowing only a specified set of characters to be entered into a given text field
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
var result = true
if mytextField == textField {
if count(string) > 0 {
let disallowedCharacterSet = NSCharacterSet(charactersInString: "0123456789.-").invertedSet
let replacementStringIsLegal = string.rangeOfCharacterFromSet(disallowedCharacterSet) == nil
result = replacementStringIsLegal
}
}
return result
}
How to program an iOS text field that takes only numeric input with a maximum length
Modern Swift
Note that a lot of the example code online is extremely out of date.
Paste the following into any Swift file in your project, example "Handy.swift".
This fixes one of the silliest problems in iOS:
Your text fields now have a .maxLength.
It is completely OK to set that value in storyboard or set in code while the app is running.
// Handy.swift
import UIKit
private var __maxLengths = [UITextField: Int]()
extension UITextField {
#IBInspectable var maxLength: Int {
get {
guard let l = __maxLengths[self] else {
return 150 // (global default-limit. or just, Int.max)
}
return l
}
set {
__maxLengths[self] = newValue
addTarget(self, action: #selector(fix), for: .editingChanged)
}
}
func fix(textField: UITextField) {
let t = textField.text
textField.text = t?.prefix(maxLength).string
}
}
It's that simple.
An even simpler one-off version...
The above fixes all text fields in the whole project.
If you just want one particular text field to simply be limited to say "4", and that's that...
class PinCodeEntry: UITextField {
override func didMoveToSuperview() {
super.didMoveToSuperview()
addTarget(self, action: #selector(fixMe), for: .editingChanged)
}
#objc private func fixMe() { text = text?.prefix(4) }
}
That's all there is to it.
(Here's a similar very useful tip relating to UITextView,
https://stackoverflow.com/a/42333832/294884 )
In Swift 4, simply use:
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
return range.location < 10
}
The same way Steven Schmatz did it but using Swift 3.0 :
//max Length
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool
{
let maxLength = 4
let currentString: NSString = textField.text! as NSString
let newString: NSString = currentString.replacingCharacters(in: range, with: string) as NSString
return newString.length <= maxLength
}
Simple solution without using a delegate:
TEXT_FIELD.addTarget(self, action: #selector(editingChanged(sender:)), for: .editingChanged)
#objc private func editingChanged(sender: UITextField) {
if let text = sender.text, text.count >= MAX_LENGHT {
sender.text = String(text.dropLast(text.count - MAX_LENGHT))
return
}
}
For Swift 5:
Just write one line to set the maximum character length:
self.textField.maxLength = 10
For more details, see Max character limit of UITextField and allowed characters Swift. (Also credited.)
I think an extension is more handy for this. See the full answer here.
private var maxLengths = [UITextField: Int]()
// 2
extension UITextField {
// 3
#IBInspectable var maxLength: Int {
get {
// 4
guard let length = maxLengths[self] else {
return Int.max
}
return length
}
set {
maxLengths[self] = newValue
// 5
addTarget(
self,
action: #selector(limitLength),
forControlEvents: UIControlEvents.EditingChanged
)
}
}
func limitLength(textField: UITextField) {
// 6
guard let prospectiveText = textField.text
where prospectiveText.characters.count > maxLength else {
return
}
let selection = selectedTextRange
// 7
text = prospectiveText.substringWithRange(
Range<String.Index>(prospectiveText.startIndex ..< prospectiveText.startIndex.advancedBy(maxLength))
)
selectedTextRange = selection
}
}
My Swift 4 version of shouldChangeCharactersIn
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
guard let preText = textField.text as NSString?,
preText.replacingCharacters(in: range, with: string).count <= MAX_TEXT_LENGTH else {
return false
}
return true
}
Other solutions posted previously produce a retain cycle due to the textfield map. Besides, the maxLength property should be nullable if not set instead of artificial Int.max constructions; and the target will be set multiple times if maxLength is changed.
Here an updated solution for Swift4 with a weak map to prevent memory leaks and the other fixes
private var maxLengths = NSMapTable<UITextField, NSNumber>(keyOptions: NSPointerFunctions.Options.weakMemory, valueOptions: NSPointerFunctions.Options.strongMemory)
extension UITextField {
var maxLength: Int? {
get {
return maxLengths.object(forKey: self)?.intValue
}
set {
removeTarget(self, action: #selector(limitLength), for: .editingChanged)
if let newValue = newValue {
maxLengths.setObject(NSNumber(value: newValue), forKey: self)
addTarget(self, action: #selector(limitLength), for: .editingChanged)
} else {
maxLengths.removeObject(forKey: self)
}
}
}
#IBInspectable var maxLengthInspectable: Int {
get {
return maxLength ?? Int.max
}
set {
maxLength = newValue
}
}
#objc private func limitLength(_ textField: UITextField) {
guard let maxLength = maxLength, let prospectiveText = textField.text, prospectiveText.count > maxLength else {
return
}
let selection = selectedTextRange
text = String(prospectiveText[..<prospectiveText.index(from: maxLength)])
selectedTextRange = selection
}
}
I give a supplementary answer based on #Frouo. I think his answer is the most beautiful way. Because it's a common control we can reuse. And there isn't any leak problem here.
private var kAssociationKeyMaxLength: Int = 0
extension UITextField {
#IBInspectable var maxLength: Int {
get {
if let length = objc_getAssociatedObject(self, &kAssociationKeyMaxLength) as? Int {
return length
} else {
return Int.max
}
}
set {
objc_setAssociatedObject(self, &kAssociationKeyMaxLength, newValue, .OBJC_ASSOCIATION_RETAIN)
self.addTarget(self, action: #selector(checkMaxLength), for: .editingChanged)
}
}
// The method is used to cancel the check when using
// the Chinese Pinyin input method.
// Becuase the alphabet also appears in the textfield
// when inputting, we should cancel the check.
func isInputMethod() -> Bool {
if let positionRange = self.markedTextRange {
if let _ = self.position(from: positionRange.start, offset: 0) {
return true
}
}
return false
}
func checkMaxLength(textField: UITextField) {
guard !self.isInputMethod(), let prospectiveText = self.text,
prospectiveText.count > maxLength
else {
return
}
let selection = selectedTextRange
let maxCharIndex = prospectiveText.index(prospectiveText.startIndex, offsetBy: maxLength)
text = prospectiveText.substring(to: maxCharIndex)
selectedTextRange = selection
}
}
Simply just check with the number of characters in the string
Add a delegate to view controller and assign the delegate
class YorsClassName : UITextFieldDelegate {
}
Check the number of characters allowed for the text field
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textField.text?.count == 1 {
return false
}
return true
}
Note: Here I checked for only characters allowed in textField.
TextField Limit Character After Block the Text in Swift 4
func textField(_ textField: UITextField, shouldChangeCharactersIn range:
NSRange,replacementString string: String) -> Bool
{
if textField == self.txtDescription {
let maxLength = 200
let currentString: NSString = textField.text! as NSString
let newString: NSString = currentString.replacingCharacters(in: range, with: string) as NSString
return newString.length <= maxLength
}
return true
}
I have something to add to Alaeddine's answer:
Your view controller should conform to UITextFieldDelegate
class MyViewController: UIViewController, UITextViewDelegate {
}
Set the delegate of your textfield:
To set the delegate, you can control drag from the textfield to your view controller in the storyboard. I think this is preferable to setting it in code
Implement the method in your view controller:
textField(_:shouldChangeCharactersInRange:replacementString:)
Update for Fattie's answer:
extension UITextField {
// Runtime key
private struct AssociatedKeys {
// Maximum length key
static var maxlength: UInt8 = 0
// Temporary string key
static var tempString: UInt8 = 0
}
// Limit the maximum input length of the textfiled
#IBInspectable var maxLength: Int {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.maxlength) as? Int ?? 0
}
set {
objc_setAssociatedObject(self, &AssociatedKeys.maxlength, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
addTarget(self, action: #selector(handleEditingChanged(textField:)), for: .editingChanged)
}
}
// Temporary string
private var tempString: String? {
get {
return objc_getAssociatedObject(self, &AssociatedKeys.tempString) as? String
}
set {
objc_setAssociatedObject(self, &AssociatedKeys.tempString, newValue, objc_AssociationPolicy.OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
// When the text changes, process the amount of text in the input
// box so that its length is within the controllable range.
#objc private func handleEditingChanged(textField: UITextField) {
// Special processing for the Chinese input method
guard markedTextRange == nil else { return }
if textField.text?.count == maxLength {
// Set lastQualifiedString where text length == maximum length
tempString = textField.text
} else if textField.text?.count ?? 0 < maxLength {
// Clear lastQualifiedString when text length > maxlength
tempString = nil
}
// Keep the current text range in arcgives
let archivesEditRange: UITextRange?
if textField.text?.count ?? 0 > maxLength {
// If text length > maximum length, remove last range and to move to -1 postion.
let position = textField.position(from: safeTextPosition(selectedTextRange?.start), offset: -1) ?? textField.endOfDocument
archivesEditRange = textField.textRange(from: safeTextPosition(position), to: safeTextPosition(position))
} else {
// Just set current select text range
archivesEditRange = selectedTextRange
}
// Main handle string maximum length
textField.text = tempString ?? String((textField.text ?? "").prefix(maxLength))
// Last configuration edit text range
textField.selectedTextRange = archivesEditRange
}
// Get safe textPosition
private func safeTextPosition(_ optionlTextPosition: UITextPosition?) -> UITextPosition {
/* beginningOfDocument -> The end of the the text document. */
return optionlTextPosition ?? endOfDocument
}
}
Set the delegate of your textfield:
textField.delegate = self
Implement the method in your view controller:
// MARK: Text field delegate
extension ViewController: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
return range.location < maxLength (maxLength can be any maximum length you can define)
}
}
Here's a Swift 3.2+ alternative that avoids unnecessary string manipulation. In this case, the maximum length is 10:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let text = textField.text ?? ""
return text.count - range.length + string.count <= 10
}
This answer is for Swift 4 and is pretty straightforward with the ability to let backspace through.
func textField(_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
return textField.text!.count < 10 || string == ""
}
This is working In Swift 4
Step 1: Set UITextFieldDelegate
class SignUPViewController: UIViewController , UITextFieldDelegate {
#IBOutlet weak var userMobileNoTextFiled: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
Step 2: Set the delegate
userMobileNoTextFiled.delegate = self // Set delegate
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// guard let text = userMobileNoTextFiled.text else { return true }
// let newLength = text.count + string.count - range.length
// return newLength <= 10
// }
Step 3: Call the function
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let maxLength = 10 // Set your need
let currentString: NSString = textField.text! as NSString
let newString: NSString =
currentString.replacingCharacters(in: range, with: string) as NSString
return newString.length <= maxLength
}
}
I use these steps. First set the delegate text field in viewdidload.
override func viewDidLoad() {
super.viewDidLoad()
textfield.delegate = self
}
And then shouldChangeCharactersIn after you include UITextFieldDelegate.
extension viewController: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let newLength = (textField.text?.utf16.count)! + string.utf16.count - range.length
if newLength <= 8 {
return true
}
else {
return false
}
}
}
Just in case, don't forget to guard the range size before applying it to the string. Otherwise, you will get a crash if the user will do this:
Type maximum length text
Insert something (nothing will be inserted due to the length limitation, but iOS doesn't know about it)
Undo insertion (you get a crash, because the range will be greater than the actual string size)
Also, using iOS 13 users can accidentally trigger this by gestures
I suggest you add to your project this
extension String {
func replace(with text: String, in range: NSRange) -> String? {
// NOTE: NSString conversion is necessary to operate in the same symbol steps
// Otherwise, you may not be able to delete an emoji, for example
let current = NSString(string: self)
guard range.location + range.length <= current.length else { return nil }
return current.replacingCharacters(in: range, with: text)
}
}
And use it like this:
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
guard let newText = textView.text.replace(with: text, in: range) else { return false }
return newText.count < maxNumberOfCharacters
// NOTE: You may wanna trim the new text instead,
// so the user will able to shove his long text at least partially
}
Otherwise, you will constantly be getting crashed in your app.
If you have multiple textField that have various length checks on one page I've found an easy and short solution.
class MultipleTextField: UIViewController {
let MAX_LENGTH_TEXTFIELD_A = 10
let MAX_LENGTH_TEXTFIELD_B = 11
lazy var textFieldA: UITextField = {
let textField = UITextField()
textField.tag = MAX_LENGTH_TEXTFIELD_A
textField.delegate = self
return textField
}()
lazy var textFieldB: UITextField = {
let textField = UITextField()
textField.tag = MAX_LENGTH_TEXTFIELD_B
textField.delegate = self
return textField
}()
}
extension MultipleTextField: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
return (range.location < textField.tag) && (string.count < textField.tag)
}
}
lazy var textField: UITextField = {
let textField = UITextField()
textField.addTarget(self, #selector(handleOnEditing), for .editingChanged)
return textField
}()
//Set Delegate in ViewDidLoad
textField.delegate = self
#objc func handleOnEditing() {
let text = textField.text ?? ""
let limit = 10
textField.text = String(text.prefix(limit))
}

Detect backspace Event in UITextField

I am searching for solutions on how to capture a backspace event, most Stack Overflow answers are in Objective-C but I need on Swift language.
First I have set delegate for the UITextField and set it to self
self.textField.delegate = self;
Then I know to use shouldChangeCharactersInRange delegate method to detect if a backspace was pressed is all code are in Objective-C. I need in Swift these following method as below is used.
-(BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
const char * _char = [string cStringUsingEncoding:NSUTF8StringEncoding];
int isBackSpace = strcmp(_char, "\b");
if (isBackSpace == -8) {
// NSLog(#"Backspace was pressed");
}
return YES;
}
Swift 4.2
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
if let char = string.cString(using: String.Encoding.utf8) {
let isBackSpace = strcmp(char, "\\b")
if (isBackSpace == -92) {
print("Backspace was pressed")
}
}
return true
}
Older Swift version
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
}
I prefer subclassing UITextField and overriding deleteBackward() because that is much more reliable than the hack of using shouldChangeCharactersInRange:
class MyTextField: UITextField {
override public func deleteBackward() {
if text == "" {
// do something when backspace is tapped/entered in an empty text field
}
// do something for every backspace
super.deleteBackward()
}
}
The shouldChangeCharactersInRange hack combined with an invisible character that is placed in the text field has several disadvantages:
with a keyboard attached, one can place the cursor before the invisible character and the backspace isn't detected anymore,
the user can even select that invisible character (using Shift Arrow on a keyboard or even by tapping on the caret) and will be confused about that weird character,
the autocomplete bar offers weird choices as long as there's only this invisible character,
Asian language keyboards that have candidate options based on the text field's text will be confused,
the placeholder isn't shown anymore,
the clear button is displayed even when it shouldn't for clearButtonMode = .whileEditing.
Of course, overriding deleteBackward() is a bit inconvenient due to the need of subclassing. But the better UX makes it worth the effort!
And if subclassing is a no-go, e.g. when using UISearchBar with its embedded UITextField, method swizzling should be fine, too.
Swift 5.3
In some version its changed and now it says:
When the user deletes one or more characters, the replacement string
is empty.
So answer for this:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if string.isEmpty {
// do something
}
return true
}
If you want to detect that some characters will be deleted
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if range.length > 0 {
// We convert string to NSString instead of NSRange to Range<Int>
// because NSRange and NSString not counts emoji as one character
let replacedCharacters = (string as NSString).substring(with: range)
}
return true
}
If you want detect backspaces even on empty textField
class TextField: UITextField {
var backspaceCalled: (()->())?
override func deleteBackward() {
super.deleteBackward()
backspaceCalled?()
}
}
Old answer
Please don't trash your code. Just put this extension somewhere in your code.
extension String {
var isBackspace: Bool {
let char = self.cString(using: String.Encoding.utf8)!
return strcmp(char, "\\b") == -92
}
}
And then just use it in your functions
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if string.isBackspace {
// do something
}
return true
}
In Swift 3
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let char = string.cString(using: String.Encoding.utf8)!
let isBackSpace = strcmp(char, "\\b")
if (isBackSpace == -92) {
print("Backspace was pressed")
}
return true
}
:)
Try this
public func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
if(string == "") {
print("Backspace pressed");
return true;
}
}
Note: You can return "true" if you want to allow backspace. Else you can return "false".
If u need detect backspace even in empty textField (for example in case if u need auto switch back to prev textField on backSpace pressing), u can use combination of proposed methods - add invisible sign and use standard delegate method textField:shouldChangeCharactersInRange:replacementString: like follow
Create invisible sign
private struct Constants {
static let InvisibleSign = "\u{200B}"
}
Set delegate for textField
textField.delegate = self
On event EditingChanged check text and if needed add invisible symbol like follow:
#IBAction func didChangeEditingInTextField(sender: UITextField) {
if var text = sender.text {
if text.characters.count == 1 && text != Constants.InvisibleSign {
text = Constants.InvisibleSign.stringByAppendingString(text)
sender.text = text
}
}
}
Add implementation of delegate method textField:shouldChangeCharactersInRange:replacementString:
extension UIViewController : UITextFieldDelegate {
// MARK: - UITextFieldDelegate
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let char = string.cStringUsingEncoding(NSUTF8StringEncoding)!
let isBackSpace = strcmp(char, "\\b")
if (isBackSpace == -92) {
if var string = textField.text {
string = string.stringByReplacingOccurrencesOfString(Constants.InvisibleSign, withString: "")
if string.characters.count == 1 {
//last visible character, if needed u can skip replacement and detect once even in empty text field
//for example u can switch to prev textField
//do stuff here
}
}
}
return true
}
}
I implemented this feature:
And in the case where the last textFiled is empty, I just want to switch to the previous textFiled. I tried all of the answers above, but no one works fine in my situation. For some reason, if I add more logic than print in isBackSpace == -92 parentheses block this method just stopped work...
As for me the method below more elegant and works like a charm:
Swift
class YourTextField: UITextField {
// MARK: Life cycle
override func awakeFromNib() {
super.awakeFromNib()
}
// MARK: Methods
override func deleteBackward() {
super.deleteBackward()
print("deleteBackward")
}
}
Thanks #LombaX for the answer
Swift 4
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let char = string.cString(using: String.Encoding.utf8)!
let isBackSpace = strcmp(char, "\\b")
if isBackSpace == -92 {
print("Backspace was pressed")
return false
}
}
Swift 4
I find the comparison using strcmp irrelevant. We don't even know how strcmp is operating behind the hoods.In all the other answers when comparing current char and \b results are -8 in objective-C and -92 in Swift. I wrote this answer because the above solutions did not work for me. ( Xcode Version 9.3 (9E145) using Swift 4.1 )
FYI : Every character that you actually type is an array of 1 or more elements in utf8 Encoding. backSpace Character is [0]. You can try this out.
PS : Don't forget to assign the proper delegates to your textFields.
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let char = string.cString(using: String.Encoding.utf8)!
if (char.elementsEqual([0])) {
print("Backspace was pressed")
}
else {
print("WHAT DOES THE FOX SAY ?\n")
print(char)
}
return true
}
Swift 4
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
//MARK:- If Delete button click
let char = string.cString(using: String.Encoding.utf8)!
let isBackSpace = strcmp(char, "\\b")
if (isBackSpace == -92) {
print("Backspace was pressed")
return true
}
}
Swift 4: If the user presses the backspace button, string is empty so this approach forces textField to only accept characters from a specified character set (in this case utf8 characters) and backspaces (string.isEmpty case).
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if string.cString(using: String.Encoding.utf8) != nil {
return true
} else if string.isEmpty {
return true
} else {
return false
}
}
Swift 5: Per the text view delegate documentation, if the replacement text returned by the shouldChangeTextIn method is empty, the user pressed the backspace button. If the range upper bound is greater than the range lower bound (or range count is 1 or more), text should be deleted. If the range upper and lower bounds are the same (or both equal 0), then there is no text to delete (a range of length 0 to be replaced by nothing). I tested, and this will get called even on an empty textfield.
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
guard text.isEmpty else { return true } // No backspace pressed
if (range.upperBound > range.lowerBound) {
print("Backspace pressed")
} else if (range.upperBound == range.lowerBound) {
print("Backspace pressed but no text to delete")
if (textView.text.isEmpty) || (textView.text == nil) {
print("Text view is empty")
}
}
return true
}
I came here looking for an answer of how to detect deletions. I wanted to know when the UITextView was empty after a user taps delete. As I was testing, I realized I needed to capture whole word deletions and cuts as well. Here's the answer I came up with.
extension SomeViewController: UITextViewDelegate {
func textView(_ textView: UITextView, shouldChangeTextIn range: NSRange, replacementText text: String) -> Bool {
let deleteTapped = text == ""
let cursorAtBeginning = range.location == 0
let deletedCharactersEqualToTextViewCount = range.length == textView.text.count
let everythingWasDeleted = deleteTapped && cursorAtBeginning && deletedCharactersEqualToTextViewCount
if everythingWasDeleted {
// Handle newly empty view from deletion
} else {
// Handle other situation
}
return true
}
}
Thanks for all the previous helpful answers. I hope this helps someone.

Max length UITextField

When I've tried How to you set the maximum number of characters that can be entered into a UITextField using swift?, I saw that if I use all 10 characters, I can't erase the character too.
The only thing I can do is to cancel the operation (delete all the characters together).
Does anyone know how to not block the keyboard (so that I can't add other letters/symbols/numbers, but I can use the backspace)?
With Swift 5 and iOS 12, try the following implementation of textField(_:shouldChangeCharactersIn:replacementString:) method that is part of the UITextFieldDelegate protocol:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let textFieldText = textField.text,
let rangeOfTextToReplace = Range(range, in: textFieldText) else {
return false
}
let substringToReplace = textFieldText[rangeOfTextToReplace]
let count = textFieldText.count - substringToReplace.count + string.count
return count <= 10
}
The most important part of this code is the conversion from range (NSRange) to rangeOfTextToReplace (Range<String.Index>). See this video tutorial to understand why this conversion is important.
To make this code work properly, you should also set the textField's smartInsertDeleteType value to UITextSmartInsertDeleteType.no. This will prevent the possible insertion of an (unwanted) extra space when performing a paste operation.
The complete sample code below shows how to implement textField(_:shouldChangeCharactersIn:replacementString:) in a UIViewController:
import UIKit
class ViewController: UIViewController, UITextFieldDelegate {
#IBOutlet var textField: UITextField! // Link this to a UITextField in Storyboard
override func viewDidLoad() {
super.viewDidLoad()
textField.smartInsertDeleteType = UITextSmartInsertDeleteType.no
textField.delegate = self
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let textFieldText = textField.text,
let rangeOfTextToReplace = Range(range, in: textFieldText) else {
return false
}
let substringToReplace = textFieldText[rangeOfTextToReplace]
let count = textFieldText.count - substringToReplace.count + string.count
return count <= 10
}
}
I do it like this:
func checkMaxLength(textField: UITextField!, maxLength: Int) {
if (countElements(textField.text!) > maxLength) {
textField.deleteBackward()
}
}
The code works for me. But I work with storyboard. In Storyboard I add an action for the text field in the view controller on editing changed.
Update for Swift 4
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let text = textField.text else { return true }
let newLength = text.count + string.count - range.length
return newLength <= 10
}
you can extend UITextField and add an #IBInspectable object for handle it:
SWIFT 5
import UIKit
private var __maxLengths = [UITextField: Int]()
extension UITextField {
#IBInspectable var maxLength: Int {
get {
guard let l = __maxLengths[self] else {
return 150 // (global default-limit. or just, Int.max)
}
return l
}
set {
__maxLengths[self] = newValue
addTarget(self, action: #selector(fix), for: .editingChanged)
}
}
#objc func fix(textField: UITextField) {
if let t = textField.text {
textField.text = String(t.prefix(maxLength))
}
}
}
and after that define it on attribute inspector
See Swift 4 original Answer
Add More detail from #Martin answer
// linked your button here
#IBAction func mobileTFChanged(sender: AnyObject) {
checkMaxLength(sender as! UITextField, maxLength: 10)
}
// linked your button here
#IBAction func citizenTFChanged(sender: AnyObject) {
checkMaxLength(sender as! UITextField, maxLength: 13)
}
func checkMaxLength(textField: UITextField!, maxLength: Int) {
// swift 1.0
//if (count(textField.text!) > maxLength) {
// textField.deleteBackward()
//}
// swift 2.0
if (textField.text!.characters.count > maxLength) {
textField.deleteBackward()
}
}
In Swift 4
10 Characters limit for text field and allow to delete(backspace)
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textField == userNameFTF{
let char = string.cString(using: String.Encoding.utf8)
let isBackSpace = strcmp(char, "\\b")
if isBackSpace == -92 {
return true
}
return textField.text!.count <= 9
}
return true
}
func checkMaxLength(textField: UITextField!, maxLength: Int) {
if (textField.text!.characters.count > maxLength) {
textField.deleteBackward()
}
}
a small change for IOS 9
Swift 3
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let nsString = NSString(string: textField.text!)
let newText = nsString.replacingCharacters(in: range, with: string)
return newText.characters.count <= limitCount
}
If you want to overwrite the last letter:
let maxLength = 10
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if range.location > maxLength - 1 {
textField.text?.removeLast()
}
return true
}
Swift 5
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textField == myTextFieldName {
if range.location > 10 {
return false
}
}
return true
}
or
func textFieldDidChangeSelection(_ textField: UITextField) {
myTextFieldName.text = String(myTextFieldName.text!.prefix(10))
}
Swift 5
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let MAX_LENGTH = 4
let updatedString = (textField.text! as NSString).replacingCharacters(in: range, with: string)
return updatedString.count <= MAX_LENGTH
}
I posted a solution using IBInspectable, so you can change the max length value both in interface builder or programmatically. Check it out here
You can use in swift 5 or swift 4 like image look like bellow
Add textField in View Controller
Connect to text to ViewController
add the code in view ViewController
class ViewController: UIViewController , UITextFieldDelegate {
#IBOutlet weak var txtName: UITextField!
var maxLen:Int = 8;
override func viewDidLoad() {
super.viewDidLoad()
txtName.delegate = self
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if(textField == txtName){
let currentText = textField.text! + string
return currentText.count <= maxLen
}
return true;
}
}
You can download Full Source form GitHub: https://github.com/enamul95/TextFieldMaxLen
Since delegates are a 1-to-1 relationship and I might want to use it elsewhere for other reasons, I like to restrict textfield length adding this code within their setup:
required init(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)!
setup()
}
required override init(frame: CGRect) {
super.init(frame: frame)
setup()
}
func setup() {
// your setup...
setMaxLength()
}
let maxLength = 10
private func setMaxLength() {
addTarget(self, action: #selector(textfieldChanged(_:)), for: UIControlEvents.editingChanged)
}
#objc private func textfieldChanged(_ textField: UITextField) {
guard let text = text else { return }
let trimmed = text.characters.prefix(maxLength)
self.text = String(trimmed)
}
Beware of the undo bug for UITextField mentioned in this post: Set the maximum character length of a UITextField
here is how you fix it in swift
if(range.length + range.location > count(textField.text)) {
return false;
}
Here is my version of code. Hope it helps!
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let invalidCharacters = NSCharacterSet(charactersInString: "0123456789").invertedSet
if let range = string.rangeOfCharacterFromSet(invalidCharacters, options: nil, range:Range<String.Index>(start: string.startIndex, end: string.endIndex))
{
return false
}
if (count(textField.text) > 10 && range.length == 0)
{
self.view.makeToast(message: "Amount entry is limited to ten digits", duration: 0.5, position: HRToastPositionCenter)
return false
}
else
{
}
return true
}
I have been using this protocol / extension in one of my apps, and it's a little more readable. I like how it recognizes backspaces and explicitly tells you when a character is a backspace.
Some things to consider:
1.Whatever implements this protocol extension needs to specify a character limit. That's typically going to be your ViewController, but you could implement character limit as a computed property and return something else, for example a character limit on one of your models.
2. You will need to call this method inside of your text field's shouldChangeCharactersInRange delegate method. Otherwise you won't be able to block text entry by returning false, etc.
3. You will probably want to allow backspace characters through. That's why I added the extra function to detect backspaces. Your shouldChangeCharacters method can check for this and return 'true' early on so you always allow backspaces.
protocol TextEntryCharacterLimited{
var characterLimit:Int { get }
}
extension TextEntryCharacterLimited{
func charactersInTextField(textField:UITextField, willNotExceedCharacterLimitWithReplacementString string:String, range:NSRange) -> Bool{
let startingLength = textField.text?.characters.count ?? 0
let lengthToAdd = string.characters.count
let lengthToReplace = range.length
let newLength = startingLength + lengthToAdd - lengthToReplace
return newLength <= characterLimit
}
func stringIsBackspaceWith(string:String, inRange range:NSRange) -> Bool{
if range.length == 1 && string.characters.count == 0 { return true }
return false
}
}
If any of you are interested, I have a Github repo where I've taken some of this character limit behavior and put into an iOS framework. There's a protocol you can implement to get a Twitter-like character limit display that shows you how far you've gone above the character limit.
CharacterLimited Framework on Github
Im using this;
Limit 3 char
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if let txt = textField.text {
let currentText = txt + string
if currentText.count > 3 {
return false
}
return true
}
return true
}
Here is my simple answer, using iOS 14+ and Xcode 12+ in Swift 5.0...
In viewDidLoad() add the following selector:
override func viewDidLoad() {
// Add a target for myTextField, pointing to .editingDidChange
myTextField.addTarget(self, action: #selector(myTextFieldDidChange(_:)), for: .editingChanged)
}
Somewhere in your class, you can also add an optional character limit:
// Add an optional character limit
let characterLimit = 100
Then later in your class, just add this function:
#objc func myTextFieldDidChange(_ textField: UITextField) {
textField.text = String(textField.text!.prefix(self.characterLimit))
}
This will limit your characters either as you type, OR when you copy+paste text into the text field.
You need to check whether the existing string plus the input is greater than 10.
func textField(textField: UITextField!,shouldChangeCharactersInRange range: NSRange, replacementString string: String!) -> Bool {
NSUInteger newLength = textField.text.length + string.length - range.length;
return !(newLength > 10)
}

Resources