Skip to content

Instantly share code, notes, and snippets.

@Julioacarrettoni
Created February 10, 2017 16:01
Show Gist options
  • Save Julioacarrettoni/dc85c5384e9e36581f14d20b5ec560bd to your computer and use it in GitHub Desktop.
Save Julioacarrettoni/dc85c5384e9e36581f14d20b5ec560bd to your computer and use it in GitHub Desktop.
Character limits
// First we need to take a look at the documentation
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string
optional public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool // return NO to not change text
- TextField: The text field containing the text.
- range: The range of characters to be replaced.
- string: The replacement string for the specified range. During typing, this parameter normally contains only the single new character that was typed, but it may contain more characters if the user is pasting text. When the user deletes one or more characters, the replacement string is empty.
//So you actualy need to combine the actual text and the new string together, not just count the character, as for example the user might be replacing a chunk of text with another using "paste" or auto correct.
//Obj-C
- (BOOL)textField:(UITextField *)textField shouldChangeCharactersInRange:(NSRange)range replacementString:(NSString *)string {
NSString* textAfterChange = [textField.text stringByReplacingCharactersInRange:range withString:string];
return textAfterChange.length < 5;
}
//This will cover ALL the cases, typing, pasting text and text replacement due to auto correct
//The equivalent in Swift 3.0 is problematic as NSRange != Range<String.Index>, you can follow an interesting discussion here:http://stackoverflow.com/questions/25138339/nsrange-to-rangestring-index
//But the right way to do it is to use UTF16 https://developer.apple.com/reference/swift/string.utf16view and this becomes rather complex super fast
//A rather safe way to do it is:
public func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if string.characters.count == 0 {
return true //User is deleting chars
}
if let r = range.toRange(), let text = textField.text {
let start = text.index(text.startIndex, offsetBy: r.lowerBound)
let end = text.index(text.startIndex, offsetBy: r.upperBound)
let substringRange = start..<end
let newStr = text.replacingCharacters(in: substringRange, with: string)
return newStr.characters.count < 5
}
return false;
}
//Or you can use an extension of string.
//Still this is not 100% safe
//The problem is that we are actually looking at the wrong callback. If we want to provice the best experience for the user we need to use: textFieldDidChange
//Why? Because otherwise everytime the user tries to do something that will result in the text going over the limit it will be silently blocked.
//Imaging the user trying to paste a text that is 1 character over the limit, the user will tap "paste" but nothing happens!
//With the following aproach in that scenario the text is pasted and then trimmed to the limit:
func textFieldDidChange(_ textField: UITextField) {
let charLimitMax = 5
if let count = textField.text?.characters.count, count >= charLimitMax {
textField.text = textField.text!.substring(to: textField.text!.index(textField.text!.startIndex, offsetBy: charLimitMax-1))
}
}
//You are thinking... I'm sure there is a catch! Theres gotta be a catch somewhere!
//Yes there is, for some reason that method is not available as a callback for UITextField, just for UITextViews, so for UITextfields you gotta do something like:
textField in textField.addTarget(self, action: #selector(textFieldDidChange(_:)), for: .editingChanged)
//There is an alternative, that is using textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool returning "false" and then assigning the trimmed text to the textfield.
//The problem with this is the shady conversion between NSRange and Range<String.Index>
//I personaly favor the textFieldDidChange aproach.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment