Why UITextField added whitespaces at the end of inputed strings? - ios

After spending time for debuging I recognized that UITextField adds whitespaces to my strings pasted from clip board.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
var input: String
let newString = string.trimmingCharacters(in: .whitespacesAndNewlines) // newString = "123"
if let oldString = textField.text { // oldString = "456"
input = oldString
input.insert(contentsOf: newString, at: input.index(input.startIndex, offsetBy: range.location)) // rang.location = 2, input = "4 123 56" but it supposed to be "412356"
} else {
input = newString
}
return true
}
I have two questions:
1- Why it happens only by pasting at second and more times?
2- How can I avoid adding these whitespaces to my pasted strings?

You are probably observing the effects of the smartInsertDeleteType property. You can change that like so:
class ViewController: UIViewController {
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
textField.smartInsertDeleteType = .no
}
}

Related

Character Count Not Working on 2nd Text Field

I'm building a simple Welcome page that has a Name & Age text input field. I was able to successfully limit the character input count on the name field, but using the same logic on the age field has not worked at all. Thoughts?
class WelcomeViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var nameTextField: UITextField!
#IBOutlet weak var ageTextField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
nameTextField.delegate = self
ageTextField.delegate = self
}
// Character Count Code UITextField
func textField(_ nameTextField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// get the current text, or use an empty string if that failed
let currentText = nameTextField.text ?? ""
// attempt to read the range they are trying to change, or exit if we can't
guard let stringRange = Range(range, in: currentText) else { return false }
// add their new text to the existing text
let updatedText = currentText.replacingCharacters(in: stringRange, with: string)
// make sure the result is under # characters
return updatedText.count <= 30
}
func textField2(_ ageTextField: UITextField, shouldChangeCharactersIn range2: NSRange, replacementString string2: String) -> Bool {
// get the current text, or use an empty string if that failed
let currentText2 = ageTextField.text ?? ""
// attempt to read the range they are trying to change, or exit if we can't
guard let stringRange2 = Range(range2, in: currentText2) else { return false }
// add their new text to the existing text
let updatedText2 = currentText2.replacingCharacters(in: stringRange2, with: string2)
// make sure the result is under # characters
return updatedText2.count <= 2
}
The delegate method has a specific signature (textField(_:shouldChangeCharactersIn:replacementString:)) that is called by the system. Your first function uses that pattern.
Your second one, currently, does not, since you've named it textField2 -- the system has no reason to know to call something starting with that prefix.
Instead, you should probably use a single function and then have it behave differently based on which UITextField is sent in to the first parameter:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textField == nameTextField {
//content
}
if textField == ageTextField {
//content
}
}
This could obviously be an else if, or even just an else if you stay limited to 2 text fields. Within the if block, you could either call out to a separate, specialized function, or keep all of your logic within the if block.
Example with switch:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
switch textfield {
case nameTextField:
//content
case ageTextField:
//content
default:
return true
}
}

Show "#" instead of "bullets" in UITextField for "Secure Text Entry"

I have a requirement to show "#" instead of bullets for password field.
But as there is no default option available for it in UITextField.
I have tried to write custom logic in "shouldChangeCharactersInRange"
But i am not able to handle the index when user will remove or add any specific character from in-between.
So here are my questions :-
1. Do i need to find any library
2. There is any other default option available for it?
3. Need to write custom logic for it? If so where i can handle it correctly "shouldChangeCharactersInRange" or "textFieldDidChange"
No you dont need to find any 3rd party library for this logic
No there is no default option available for your need
Yes, you need to write a custom logic for your demand, So here it goes...
var passwordText = String()
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textField == textFieldPassword {
var hashPassword = String()
let newChar = string.characters.first
let offsetToUpdate = passwordText.index(passwordText.startIndex, offsetBy: range.location)
if string == "" {
passwordText.remove(at: offsetToUpdate)
return true
}
else { passwordText.insert(newChar!, at: offsetToUpdate) }
for _ in passwordText.characters { hashPassword += "#" }
textField.text = hashPassword
return false
}
Swift 4:-
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if textField == textFieldPassword {
var hashPassword = String()
let newChar = string.first
let offsetToUpdate = passwordText.index(passwordText.startIndex, offsetBy: range.location)
if string == "" {
passwordText.remove(at: offsetToUpdate)
return true
}
else { passwordText.insert(newChar!, at: offsetToUpdate) }
for _ in 0..<passwordText.count { hashPassword += "#" }
textField.text = hashPassword
return false
}
return true
}
Use a normal textfield without the secure input option. When a user enters a character, save it to a string variable, and replace it in the textfield with the character you wish to present instead of the bullets.
class ViewController: UIViewController,UITextFieldDelegate {
let textField = UITextField(frame :CGRect(x:16,y:50,width:200,height: 40))
override func viewDidLoad() {
super.viewDidLoad()
textField.delegate = self
self.view.addSubview(textField)
textField.becomeFirstResponder()
}
var password: String = ""
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool{
password = password+string
textField.text = textField.text!+"#"//Character you want
print("\(password)")
return false
}
}
This is in Swift 2. Hope it Helps!!
Improved Mr. Bean's answer in swift 5. To fix Copy&Paste bugs.
var passNSString : NSString = ""
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
var hashPassword = String()
passNSString = passNSString.replacingCharacters(in: range, with: string) as NSString
for _ in 0..<passNSString.length { hashPassword += "#" }
textField.text = hashPassword
print("str", passNSString)
return false
}

Swift 3: Append to UITextField while Editing Changed

Probably a simple one although no answers updated for Swift 3.
How can I append characters to a UITextField while the textfield is being edited? The characters should be appended while the user types not after the editing ends.
For example:
User types: 1
Field Updates: 1 kg
User types 123
Field Updates: 123 kg
Was trying to tackle this using EditingChanged IBActions but how can I stop the value from appending "kg" for each new character that is typed?
Example "1 kg 2 kg 3 kg"
Try this way it may help you out.
Add target for textfield on text is being editing
textField.addTarget(self, action: #selector(self.textFieldDidChange), for: .editingChanged)
In Observer method try the following way
func textFieldDidChange(textfield: UITextField) {
var text = textField.text?.replacingOccurrences(of: " KG", with: "")
text = text! + " KG"
textField.text = text
print("Text changed")
}
You want the UITextFieldDelegate method
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool
I should warn you that this is incredibly irritating to implement because of the way Swift handles Strings. Its usually better to just cast the text to NSString (which is UTF16) and deal with the range that way. However if you are just doing numbers and can live with a fixed decimal place the case is much easier to handle. Keep a custom number that represents your "real number" and just update the field to reflect your formatted number. Since you only allow digits there are finitely many cases to handle (this code will not handle copy/paste).
You must set the textfield delegate and keyboard (to numeric) in the storyboard.
class ViewController: UIViewController{
#IBOutlet weak var textField: UITextField!
fileprivate let digits: Set<String> = ["0","1","2","3","4","5","6","7","8","9"]
fileprivate let decimalPlaces = 2
fileprivate let suffix = " kg"
fileprivate lazy var formatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.minimumFractionDigits = self.decimalPlaces
formatter.maximumFractionDigits = self.decimalPlaces
formatter.locale = NSLocale.current
return formatter
}()
fileprivate var amount: Int = 0
override func viewDidLoad() {
super.viewDidLoad()
}
}
extension ViewController: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if digits.contains(string) {
amount *= 10
amount += Int(string)!
} else if string == "" {
amount /= 10
}
guard amount > 0 else {
textField.text = ""
return false
}
let digitsAfterDecimal = formatter.maximumFractionDigits
var value = Double(amount)
for _ in 0..<digitsAfterDecimal {
value /= 10
}
textField.text = formatter.string(from: NSNumber(value: value))! + suffix
return false
}
}
Conform the textfield delegate to the controller and try this solution.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if string == "" {
// handle backspace scenario here
return true
} else if var textString = textField.text {
let unitString = "kg"
if textString.contains(unitString) {
textString = textString.replacingOccurrences(of: unitString, with: "")
textString += string + unitString
textField.text = textString
} else {
textField.text = string + unitString
}
return false
}
return true
}
Use UITextFieldDelegate's function:
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool // return NO to not change text
You'll have to combine the text from the textfield.text and string parameter from this function using the range parameter to get the string To Be formed on textfield while you type/paste/clear based on range.
Keep track on range.length as it always gives the count of string being deleted/cleared and is 0 when you enter text. And range.location gives the position of edit.
Then you can set the formed string to textfield.text and return false Remember - return false and not true

Blocking some of number pad's numbers so that it is adapted to the user input.

My app asks the user for a precise number of minutes which she/he inserts using a number pad keyboard. I'd like to block the keyboard's numbers >5 for the first number the user types (so that they can't type a number of minutes > 59). For the second number, I'd like it to unblock the previously blocked numbers so that the minutes can be precisely inserted (e.g. 18 or 59). Is there a way to do this? If so, how?
You should work with
textField(_:shouldChangeCharactersIn:replacementString:) from the UITextFieldDelegate.
Here is a working example of what you can do:
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
var startString = NSString()
if textField.text != nil {
startString = textField.text! as NSString
}
let endString = startString.stringByReplacingCharactersInRange(range, withString: string)
if endString.isEmpty == true {
return true
}
let intValue = Int(endString)
if intValue >= 0 && intValue < 60 {
return true
}
return false
}
In this case, it will allow every integer value between 0 (included) and 60 (excluded).
Edit:
This method is called by the UITextField on it's delegate (if you don't understand what is a delegate, take some time to read this and this, this pattern is very important).
So, to make the method above works, you just have to say to your UITextField that your UIViewController is its delegate. You can do it in the code (in viewDidLoad for example) or in Interface Builder. Here is a fully working example:
class MyViewController: UIViewController, UITextFieldDelegate {
#IBOutlet weak var textField: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
textField.delegate = self
}
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
var startString = NSString()
if textField.text != nil {
startString = textField.text! as NSString
}
let endString = startString.stringByReplacingCharactersInRange(range, withString: string)
if endString.isEmpty == true {
return true
}
let intValue = Int(endString)
if intValue >= 0 && intValue < 60 {
return true
}
return false
}
}

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

Resources