Reputation: 9918
I have a textfield and I want to limit the entry to max 2 decimal places.
number like 12.34 is allowed but not 12.345
How do I do it?
Upvotes: 22
Views: 20896
Reputation: 1623
Following the Code Different's answer, I've improved the code to support a different Locale and different UITextFields in the same class.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard textField.keyboardType == .decimalPad, let oldText = textField.text, let r = Range(range, in: oldText) else {
return true
}
let newText = oldText.replacingCharacters(in: r, with: string)
let isNumeric = newText.isEmpty || (Double(newText) != nil)
let formatter = NumberFormatter()
formatter.locale = Locale.current
let decimalPoint = formatter.decimalSeparator ?? "."
let numberOfDots = newText.components(separatedBy: decimalPoint).count - 1
let numberOfDecimalDigits: Int
if let dotIndex = newText.index(of: ".") {
numberOfDecimalDigits = newText.distance(from: dotIndex, to: newText.endIndex) - 1
} else {
numberOfDecimalDigits = 0
}
return isNumeric && numberOfDots <= 1 && numberOfDecimalDigits <= 2
}
Upvotes: 2
Reputation: 101
Swift 5. Adding to Enrique's answer (based on Code Different baseline), I found the need to allow the Delete key to operate anywhere on the line along with a sign (+ or -) or decimal point at the beginning of the input line.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// Limit number fields to +/- Real #s: sign as first character, one decimal separator, one decimal place after separator
if string.isEmpty { return true } // Allow delete key anywhere!
guard let oldText = textField.text, let rng = Range(range, in: oldText) else {
return true
}
let newText = oldText.replacingCharacters(in: rng, with: string)
let isNumeric = newText.isEmpty || (Double(newText) != nil)
let formatter = NumberFormatter()
formatter.locale = .current
let decimalPoint = formatter.decimalSeparator ?? "."
let numberOfDots = newText.components(separatedBy: decimalPoint).count - 1
let numberOfDecimalDigits: Int
if let dotIndex = newText.firstIndex(of: ".") {
numberOfDecimalDigits = newText.distance(from: dotIndex, to: newText.endIndex) - 1
} else {
numberOfDecimalDigits = 0
}
if newText.count == 1 && !isNumeric { // Allow first character to be a sign or decimal point
return CharacterSet(charactersIn: "+-" + decimalPoint).isSuperset(of: CharacterSet(charactersIn: string))
}
return isNumeric && numberOfDots <= 1 && numberOfDecimalDigits <= 1
}
Upvotes: 0
Reputation: 117
Only Two decimal point allow and only one "." allow
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let text: NSString = textField.text! as NSString
let resultString = text.replacingCharacters(in: range, with: string)
//Check the specific textField
if textField == costTextField {
let textArray = resultString.components(separatedBy: ".")
if textArray.count > 2 { //Allow only one "."
return false
}
if textArray.count == 2 {
let lastString = textArray.last
if lastString!.count > 2 { //Check number of decimal places
return false
}
}
}
return true
}
Upvotes: 5
Reputation: 773
None of the answers handled the decimalSeparator and all the edge cases I came across so I decided to write my own.
extension YourController: UITextFieldDelegate {
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let text = textField.text, let decimalSeparator = NSLocale.current.decimalSeparator else {
return true
}
var splitText = text.components(separatedBy: decimalSeparator)
let totalDecimalSeparators = splitText.count - 1
let isEditingEnd = (text.count - 3) < range.lowerBound
splitText.removeFirst()
// Check if we will exceed 2 dp
if
splitText.last?.count ?? 0 > 1 && string.count != 0 &&
isEditingEnd
{
return false
}
// If there is already a dot we don't want to allow further dots
if totalDecimalSeparators > 0 && string == decimalSeparator {
return false
}
// Only allow numbers and decimal separator
switch(string) {
case "", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", decimalSeparator:
return true
default:
return false
}
}
}
Upvotes: 2
Reputation: 119
In Swift 4.
TextField have 10 digit limit and after decimal 2 digit limit (you can change the Limits). The dot will allow only one time in the textField.
class ViewController: UIViewController,UITextFieldDelegate {
var dotLocation = Int()
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let nonNumberSet = CharacterSet(charactersIn: "0123456789.").inverted
if Int(range.length) == 0 && string.count == 0 {
return true
}
if (string == ".") {
if Int(range.location) == 0 {
return false
}
if dotLocation == 0 {
dotLocation = range.location
return true
} else {
return false
}
}
if range.location == dotLocation && string.count == 0 {
dotLocation = 0
}
if dotLocation > 0 && range.location > dotLocation + 2 {
return false
}
if range.location >= 10 {
if dotLocation >= 10 || string.count == 0 {
return true
} else if range.location > dotLocation + 2 {
return false
}
var newValue = (textField.text as NSString?)?.replacingCharacters(in: range, with: string)
newValue = newValue?.components(separatedBy: nonNumberSet).joined(separator: "")
textField.text = newValue
return false
} else {
return true
}
}
}
Upvotes: 6
Reputation: 2417
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let dotString = "."
if let text = textField.text {
let isDeleteKey = string.isEmpty
if !isDeleteKey {
if text.contains(dotString) {
if text.components(separatedBy: dotString)[1].count == 2 {
return false
}
}
}
}
return true
}
Upvotes: 1
Reputation: 1571
Guys, here's the solution:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let dotString = "."
if let text = textField.text {
let isDeleteKey = string.isEmpty
if !isDeleteKey {
if text.contains(dotString) {
if text.components(separatedBy: dotString)[1].count == 2 {
return false
}
}
}
}
}
Upvotes: 5
Reputation: 93161
Set your controller as the delegate for the text field and check if the proposed string satisfy your requirements:
override func viewDidLoad() {
super.viewDidLoad()
textField.delegate = self
textField.keyboardType = .decimalPad
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let oldText = textField.text, let r = Range(range, in: oldText) else {
return true
}
let newText = oldText.replacingCharacters(in: r, with: string)
let isNumeric = newText.isEmpty || (Double(newText) != nil)
let numberOfDots = newText.components(separatedBy: ".").count - 1
let numberOfDecimalDigits: Int
if let dotIndex = newText.index(of: ".") {
numberOfDecimalDigits = newText.distance(from: dotIndex, to: newText.endIndex) - 1
} else {
numberOfDecimalDigits = 0
}
return isNumeric && numberOfDots <= 1 && numberOfDecimalDigits <= 2
}
Upvotes: 53