145

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

Community
  • 1
  • 1
giorgionocera
  • 6,428
  • 6
  • 20
  • 17

20 Answers20

342

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
    }

}
Imanou Petit
  • 89,880
  • 29
  • 256
  • 218
  • 1
    Do you just put this code in your view controller class? Or do I have to make connections? – Isaac Wasserman Jun 28 '15 at 20:11
  • If someone needs to put some condition..you can do like this.. . if (textField .isEqual(mobileNumberTextfield)) { guard let text = textField.text else { return true } let newLength = text.characters.count + string.characters.count - range.length return newLength <= limitLength; } return true; – Narasimha Nallamsetty Nov 25 '15 at 13:49
  • 7
    For Swift 4, `text.characters.count` is deprecated use `text.count` – Mohamed Salah Nov 10 '17 at 06:07
52

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.

GRme
  • 2,707
  • 5
  • 26
  • 49
  • 2
    countElements has been changed to count in Swift 2, but changing that works for me! – Jay Aug 24 '15 at 17:11
  • 1
    Thanks, you can use now textField.text?.characters.count since countElements has been changed. – Anibal R. Jul 08 '16 at 21:14
  • 1
    Tks, it worked great with this change: countElements(textField.text!) in Swift 2 is: textField.text?.characters.count – kha Jul 19 '16 at 16:57
42

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
}
Shan Ye
  • 2,622
  • 19
  • 21
23

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

mohsen
  • 4,698
  • 1
  • 33
  • 54
16

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()
    }
}
Sruit A.Suk
  • 7,073
  • 7
  • 61
  • 71
12

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
    }
Sai kumar Reddy
  • 1,751
  • 20
  • 23
10
func checkMaxLength(textField: UITextField!, maxLength: Int) {
        if (textField.text!.characters.count > maxLength) {
            textField.deleteBackward()
        }
}

a small change for IOS 9

Mr H
  • 5,254
  • 3
  • 38
  • 43
Jeremy Andrews
  • 807
  • 12
  • 17
8

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
    }
Basil Mariano
  • 2,437
  • 1
  • 20
  • 13
6

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
}
maxwell
  • 3,788
  • 6
  • 26
  • 40
6

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))
}
awex
  • 130
  • 3
  • 7
5

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
    }
TDK
  • 91
  • 1
  • 4
4

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

Community
  • 1
  • 1
frouo
  • 5,087
  • 3
  • 26
  • 29
4

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

Enamul Haque
  • 4,789
  • 1
  • 37
  • 50
2

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)

        }
MQLN
  • 2,292
  • 2
  • 18
  • 33
1

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;
}
Community
  • 1
  • 1
Horatio
  • 1,695
  • 1
  • 18
  • 27
  • If you want to support emoji and such use: if (range.length + range.location > count(textField.text.utf16)){ return false; } – AlexD Aug 10 '15 at 03:31
1
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
    }
Alvin George
  • 14,148
  • 92
  • 64
1

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

Theo Bendixson
  • 581
  • 4
  • 12
1

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
    }
ali ozkara
  • 5,425
  • 2
  • 27
  • 24
-1

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.

WhiteEagle
  • 97
  • 1
  • 4
-3

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)
   }
bhzag
  • 2,932
  • 7
  • 23
  • 39
  • 5
    Your code is wrong. 1. You have to declare your constant or variable with let or var in Swift (not NSUInteger). 2. textField.text and string are of type String. Length is not a property/method of String in Swift. – Imanou Petit Aug 10 '14 at 00:21