135
votes

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

20

20 Answers

328
votes

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
    }

}
50
votes

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.

36
votes

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

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

enter image description here

See Swift 4 original Answer

15
votes

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()
    }
}
12
votes

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
    }
9
votes
func checkMaxLength(textField: UITextField!, maxLength: Int) {
        if (textField.text!.characters.count > maxLength) {
            textField.deleteBackward()
        }
}

a small change for IOS 9

8
votes

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

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

I posted a solution using IBInspectable, so you can change the max length value both in interface builder or programmatically. Check it out here

4
votes

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

You can use in swift 5 or swift 4 like image look like bellow enter image description here

  1. Add textField in View Controller
  2. Connect to text to ViewController
  3. 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

2
votes

Swift 5

func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
    if textField == myTextFieldName {
        if range.location > 10 {
            return false
        }
    }
    return true
}
1
votes

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;
}
1
votes
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
    }
1
votes

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

1
votes

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)

        }
0
votes

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

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.

-3
votes

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