I want the user to enter a Social Security Number in the format ••• •• ••••.
The user types the first 3 numbers, then I append a space manually. Then they enter 2 more numbers and I manually append a space. Of course, even the spaces are being displayed as •. Is there a native way to change this behavior? I am currently using a funky manual implementation of this.
What if instead of spaces, you use three different text entries? Then when each user enters the first three characters, you jump to the second text entry? When he types two more, you jump to the third text entry.
Here is an example:
EDIT: Now supports backspace (Thanks to #triple-s).
extension ViewController: UITextFieldDelegate {
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
switch textField {
// Jump forwards
case self.textField1 where string.characters.count == 3 :
self.textField2.becomeFirstResponder()
case self.textField2 where string.characters.count == 2 :
self.textField3.becomeFirstResponder()
// Jump backwards
case self.textField3 where string.characters.count == 0 :
self.textField2.becomeFirstResponder()
case self.textField2 where string.characters.count == 0 :
self.textField1.becomeFirstResponder()
default :
break
}
return true
}
}
This can be achieve in one single textField as asked. I only tapped "1", in the gif.
You select your keypad type to be number (0-9), which can ensure everything that will be input there is number only.
Then you can adopt the textField delegate and implement the delegate method
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
print(string)
let char = string.cStringUsingEncoding(NSUTF8StringEncoding)!
let isBackSpace = { return strcmp(char, "\\b") == -92}
if (textField.text?.characters.count == 3 && !isBackSpace()) || (textField.text?.characters.count == 6 && !isBackSpace()){
textField.text = textField.text! + " "
}
if (textField.text?.characters.count) == 11 && !isBackSpace(){
ssnString = textField.text!
self.view.endEditing(true)
}
return true
}
This includes the logic of adding space after third digit if you are not backspacing and same for the after 6th digit.
Also, after user input 11 digit, it will not allow user to input more number as the format of SSN, after 11 digit is input, the SSN is saved in ssnString, which will be used for you special masking.
Because you don't want to mask space, we can not use secureTextEntry. So in the didEndEditing, I gave an condition only if the user enter the full SSN, we will mask it, which can be modified to any scenario if you want. But i think this makes more sense.
func textFieldDidEndEditing(textField: UITextField) {
if textField.text?.characters.count == 11 {
maskSSNTextField()
}
}
In the maskSSNTextField method,
func maskSSNTextField() {
textField.text = "••• •• ••••"
}
Finally, we need to unmask it when user come back to it, if they want to change the text
func textFieldDidBeginEditing(textField: UITextField) {
if textField.text == "••• •• ••••"{
textField.text = ssnString
}
}
This fully fulfilled your requirement. Please let me know if you have other question.
I changed the didChangeInRange method to meet your new requirement, although I think my previous answer could work. Now it works as in the gif. If you want it to be still masked, you can change the code in textField did begin editing.
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
let char = string.cStringUsingEncoding(NSUTF8StringEncoding)!
let isBackSpace = { return strcmp(char, "\\b") == -92}
if (textField.text?.characters.count == 3 && !isBackSpace()) || (textField.text?.characters.count == 6 && !isBackSpace()){
textField.text = textField.text! + " "
ssnString = ssnString + " "
}
if isBackSpace() {
ssnString = ssnString.substringToIndex(ssnString.endIndex.predecessor())
}else {
ssnString = ssnString + string
print(ssnString)
if ssnString.characters.count >= 2 {
var starString = ""
for i in 0...ssnString.characters.count-2 {
if i==3 || i==6 {
starString = starString+" "
}else {
starString = starString+"•"
}
}
textField.text = ""
print(ssnString.characters.last)
textField.text = starString
}
}
if (textField.text?.characters.count) == 11 && !isBackSpace(){
self.view.endEditing(true)
}
return true
}
The simple solution I have been using is to convert my input string to an NSAttributedString with text spacing (.kern) attributes added at the proper locations and keeping isSecureTextEntry set to true.
Disabling isSecureTextEntry and doing it by hand in addition of being overly complex could have security implications at least if someone is using a third party keyboard.
var ssnText = "123456789"
let spacerPositions = [ 2, 4 ]
let spacingAmount: CGFloat = 5.0
let spacerRanges:[NSRange] = spacerPositions
.filter { $0 < ssnText.count - 1 }
.map { NSRange(location: $0, length: 1) }
let attributedString = NSMutableAttributedString(string: ssnText)
for range in spacerRanges {
attributedString.addAttribute(.kern, value: spacingAmount, range: range)
}
textField.attributedText = attributedString
calling that stuff in textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String).
Related
Hello everyone!) Need some help!)
How can I allow the user to enter only one sentence (word or characters) in a TextField with some rules?)
The user must enter only this word:
Qwerty
Then text field must shows automatically hyphen:
Qwerty-
And after that the user can type in text field only this digits:
12345
Expected result must be only this:
Qwerty-12345
The order of entering each letter or number is very important!)
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let text = onlyCharTextField.text else { return true }
if textField == onlyCharTextField {
let allowedCharacters = "Q-w-e-r-t-y "
let allowedCharacterSet = CharacterSet(charactersIn: allowedCharacters)
let allowedDigits = "1-2-3-4-5"
let allowedDigitSet = CharacterSet(charactersIn: allowedDigits)
if text == "Qwerty" {
onlyCharTextField.text = "Qwerty" + "-"
}
let typedCharacterSet = CharacterSet(charactersIn: string)
let alphabet = allowedCharacterSet.isSuperset(of: typedCharacterSet) || allowedDigitSet.isSuperset(of: typedCharacterSet)
return alphabet
} else {
return false
}
}
I'm confused..(( Do you have any ideas how to implement this?)
Thanks for every answer!)
It's not that complicated. You can use the "first compute what the new text would be" pattern, and then check if "Qwerty-12345" starts with that text. This is because if the text is entered in the correct order, then the text is always a part (or the entirety) of the beginning of "Qwerty-12345":
Q
Qw
Qwe
Qwer
Qwert
Qwerty-
Qwerty-1
Qwerty-12
Qwerty-123
Qwerty-1234
Qwerty-12345
A special case is when the text is Qwerty. This is when you don't allow the keyboard to change the text, and instead change it programmatically to Qwerty-.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let text = (textField.text ?? "") as NSString
let newText = text.replacingCharacters(in: range, with: string)
if newText == "Qwerty" {
textField.text = "Qwerty-"
return false
}
return "Qwerty-12345".starts(with: newText)
}
Note that this still allows the user to delete what they have already entered, but only from the end, and enter Qwerty-12345 from the deleted point. If you want to disallow that, you can check if the replacementString parameter is empty, which is indicative of a deletion:
if string.isEmpty {
return false
}
This doesn't disable pasting. If you want that, see How to disable pasting in a TextField in Swift?
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let expectedText = "Qwerty-12345" //Text u want user to enter
let halfText = "Qwerty" //special case where u enter an additional -
let enteredText = "\(textField.text ?? "")\(string)" //get the text user has entered
if enteredText == expectedText.prefix(enteredText.count){ //check if the user entered text matches to the the first part of your string
// if it matches change the text of the text field
if enteredText == halfText{
textField.text = "\(enteredText)-" //special case where u add an -
}
else{
textField.text = enteredText
}
}
return false
}
How would I autofill code from sms into 6 UITextfields containing 1 digit each?
First, you need to assign a tag to each of UITextfield like below if you have an array of UITextfields.
for i in 1 ... textFields.count {
textFields[i].tag = i
textFields[i].delegate = self
textFields[i].textContentType = .oneTimeCode
}
After that, you have to use one of the UITextfield delegates method shouldChangeCharactersIn.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if string.trimmingCharacters(in: CharacterSet.whitespaces).count != 0 {
textField.text = string
if textField.tag < textFields.count {
let next = textField.superview?.viewWithTag(textField.tag + 1)
next?.becomeFirstResponder()
} else if textField.tag == textFields.count {
textField.resignFirstResponder()
}
} else if string.count == 0 { // is backspace
textField.text = ""
}
return false
}
If you need an example and the best solution for this scenario, I would highly recommend my own Github repo: https://github.com/Datt1994/DPOTPView
I want to achieve following:
Have a decimal keypad. Which means user will be able to enter Double
values. (Needless to say "." will be limited one)
Prevent "0" characters as the first characters. (i.e.: There should
not be values like "003" "01" "000012" etc.)
Limit the character count to 10.
Only allow numbers. No Copy-Paste text values.
I am using decimal keypad. Below code handles first and third item above:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let currentString: NSString = (textField.text ?? "") as NSString
let newString = currentString.replacingCharacters(in: range, with: string)
return newString.count <= 10
}
Thank you for your time.
Bellow code will check all conditions you have specified
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
//Prevent "0" characters as the first characters. (i.e.: There should not be values like "003" "01" "000012" etc.)
if textField.text?.count == 0 && string == "0" {
return false
}
//Limit the character count to 10.
if ((textField.text!) + string).count > 10 {
return false
}
//Have a decimal keypad. Which means user will be able to enter Double values. (Needless to say "." will be limited one)
if (textField.text?.contains("."))! && string == "." {
return false
}
//Only allow numbers. No Copy-Paste text values.
let allowedCharacterSet = CharacterSet.init(charactersIn: "0123456789.")
let textCharacterSet = CharacterSet.init(charactersIn: textField.text! + string)
if !allowedCharacterSet.isSuperset(of: textCharacterSet) {
return false
}
return true
}
You can use regex that define a number with <= 10 digits and not starting with 0, then use NSPredicate or NSRegularExpression to validate the entered text. Something like this:
func isAllowed(str: String?) -> Bool {
let regexPattern: String = "^((?!(0))[0-9]{0,10})$"
let predicate = NSPredicate(format:"SELF MATCHES %#", regexPattern)
return predicate.evaluate(with: str)
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
return isAllowed(str: textField.text)
}
You can Create this method to prevent from copy paste
override func canPerformAction(action: Selector, withSender sender: AnyObject?) -> Bool {
if action == "paste:" {
return false
}
return super.canPerformAction(action, withSender: sender)
}
and also you can add the following code to shouldChangeCharactersInRange Delegate method of textfield
let searchString = (txtMobilePhone.text as NSString?)?.replacingCharacters(in: range, with: string)
if (searchString?.length)! > 1 {
let inverseSet = CharacterSet(charactersIn: ".0123456789").inverted
return ((string as NSString).rangeOfCharacter(from: inverseSet).location == NSNotFound)
} else {
let inverseSet = CharacterSet(charactersIn: ".123456789").inverted
return ((string as NSString).rangeOfCharacter(from: inverseSet).location == NSNotFound)
}
this above will only allow the user to enter the "0" after the second character I mean restricts the user to type "0" at the starting of numbers
Am very new to swift language, Any help is appreciable, Here is my code
public func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
if textField.text!.isEmpty {
textField.text = "0.00"
}
let range2 = string.rangeOfCharacterFromSet(NSMutableCharacterSet.decimalDigitCharacterSet())
if range2 != nil{
// textField.text = "M"
var f = textField.text?.floatValue
f=f!*10.0
f=f!+(string.floatValue/100.0)
textField.text = NSString(format: "%.2f", f!) as String
return false;
}
var f = textField.text?.floatValue
f=f!/10.0
textField.text = NSString(format: "%.2f", f!) as String
return false;
}
When am entering the numbers in texfield it's taking the numbers upto infinity. Please help me how to restrict the textfield to allow only 4 digits in my code.
First point, shouldChangeCharactersInRange is not a method for side effects. It should not be mutating any state, it should not change the value of the text field. It should merely return either true or false.
As I understand it, you want to disallow entry of more than four digits to the left of the decimal point.
extension ViewController: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let newText = ((textField.text ?? "") as NSString).replacingCharacters(in: range, with: string)
let number = Float(newText)
return newText.isEmpty || newText == "." || number != nil && number! < 10000
}
}
I have an iOS Xcode 7.3 Swift2 project I'm working on. It has different UITextFields that are limited to 3 digits, specifically only numbers. They are assigned to the UITextFieldDelegate and it's working well.
Here is where I limit them:
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
guard let text = textField.text else { return true }
let newLength = text.characters.count + string.characters.count - range.length
let limitLength = 3
if newLength > limitLength {
return false
}
let numberOnly = NSCharacterSet.init(charactersInString: "0123456789")
let stringFromTextField = NSCharacterSet.init(charactersInString: string)
let strValid = numberOnly.isSupersetOfSet(stringFromTextField)
return strValid
}
However, some of the UITextFields need to be limited to numbers still AND also limited to a single digit, how can I institute this in the section above, only for those specific UITextFields?
The names of the UITextFields that need to be single digits are:
widthInches
lengthInches
I tried placing this after the first guard section with no luck:
guard let text2 = widthInches.text else { return true }
let newLength2 = text2.characters.count + string.characters.count - range.length
let limitLength2 = 3
if newLength2 > limitLength2 {
return false
}
You can also try this code for limit textfield
actually i am using here textfield tag. Because custom textfield.
If you using custom textfield like TextfieldEffect in this condition tag will help you for limit of Textfield.
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool{
if textField.tag == txtCountryCode.tag{
let maxLength = 4
let currentString: NSString = textField.text!
let newString: NSString =
currentString.stringByReplacingCharactersInRange(range, withString: string)
return newString.length <= maxLength
}
if textField.tag == txtMobileNumber.tag{
let maxLength = 10
let currentString: NSString = textField.text!
let newString: NSString =
currentString.stringByReplacingCharactersInRange(range, withString: string)
return newString.length <= maxLength
}
return true
}
I hope this will help you.
The function shouldChangeCharactersInRange passes in the particular textField as one of its parameters. You can look at that and see if it points to the same instance as the ones you want to shorten, like this:
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
guard let text = textField.text else { return true }
var limitLength = 3
if textField == widthInches || textField == lengthInches {
limitLength = 1
}
let newLength = text.characters.count + string.characters.count - range.length
if newLength > limitLength {
return false
}
let numberOnly = NSCharacterSet.init(charactersInString: "0123456789")
let stringFromTextField = NSCharacterSet.init(charactersInString: string)
let strValid = numberOnly.isSupersetOfSet(stringFromTextField)
return strValid
}
Assuming all other requirements are the same (numbers only) this will do the trick.
There are other ways, for example - you could subclass UITextField and add a limitLength field, then use that field in the delegate, but that's probably overkill for just 2 exceptions.
Hello in your func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool the textField param is the textField that has trigger this event so you can check with yours textfields objects and if are equal to one of them then make a different behavior
I hope this helps you,
func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool {
return (textField.text?.utf16.count ?? 0) + string.utf16.count - range.length <= TEXT_FIELD_LIMIT
}
This counts the number of characters based on UTF-16 representation, as range.length is given in UTF-16 base. If you need to count the number of characters in other ways, the expression may get longer. If you want only numbers to be input use textField.keyboardType = UIKeyboardTypeDecimalPad . If you want specific textFields then add tags and compare them and if they are equal you can implement your specific code for that.
Check this link for detailed answer :
http://www.globalnerdy.com/2016/05/24/a-better-way-to-program-ios-text-fields-that-have-maximum-lengths-and-accept-or-reject-specific-characters/
update for swift 3 add this class and call it TextField.swift. it will add the limit input on the storyboard.
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
// Any text field with a set max length will call the limitLength
// method any time it's edited (i.e. when the user adds, removes,
// cuts, or pastes characters to/from the text field).
addTarget(
self,
action: #selector(limitLength),
for: UIControlEvents.editingChanged
)
}
}
func limitLength(textField: UITextField) {
guard let prospectiveText = textField.text,
prospectiveText.characters.count > maxLength else {
return
}
// If the change in the text field's contents will exceed its maximum
length,
// allow only the first [maxLength] characters of the resulting text.
let selection = selectedTextRange
// text = prospectiveText.substring(with:Range<String.Index>
(prospectiveText.startIndex ..< prospectiveText.index(after: maxLength))
let s = prospectiveText
// Get range 4 places from the start, and 6 from the end.
let c = s.characters;
let r = c.index(c.startIndex, offsetBy: 0)..<c.index(c.endIndex, offsetBy: maxLength - c.count)
text = s[r]
// Access the string by the range.
selectedTextRange = selection
}
}
or download here - >TextField.swift