I want to have commas dynamically added to my numeric UITextField
entry while the user is typing.
For example: 123,456
and 12,345,678
Here is a solution in swift 4.
@objc func textFieldValDidChange(_ textField: UITextField) {
let formatter = NumberFormatter()
formatter.numberStyle = NumberFormatter.Style.decimal
if textField.text!.count >= 1 {
let number = Double(bottomView.balanceTxtField.text!.replacingOccurrences(of: ",", with: ""))
let result = formatter.string(from: NSNumber(value: number!))
textField.text = result!
}
}
Don't forget to add editingChanged action as below:
textField.addTarget(self, action:#selector(ViewController.textFieldValDidChange), for: .editingChanged)
Here is a version in Swift 4. I used it for integers, I didn't check with decimal numbers.
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
// Uses the number format corresponding to your Locale
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.locale = Locale.current
formatter.maximumFractionDigits = 0
// Uses the grouping separator corresponding to your Locale
// e.g. "," in the US, a space in France, and so on
if let groupingSeparator = formatter.groupingSeparator {
if string == groupingSeparator {
return true
}
if let textWithoutGroupingSeparator = textField.text?.replacingOccurrences(of: groupingSeparator, with: "") {
var totalTextWithoutGroupingSeparators = textWithoutGroupingSeparator + string
if string.isEmpty { // pressed Backspace key
totalTextWithoutGroupingSeparators.removeLast()
}
if let numberWithoutGroupingSeparator = formatter.number(from: totalTextWithoutGroupingSeparators),
let formattedText = formatter.string(from: numberWithoutGroupingSeparator) {
textField.text = formattedText
return false
}
}
}
return true
}
The big advantage of this method is that it uses the grouping separator defined in your current locale (region), because not everybody uses the comma as a grouping separator.
Works with 0, backspace, but, again, I didn't test it with decimals. You are free to enhance this code if you worked it out with decimals.
Examples:
Starting 0 works too:
I have another one. Fix some bugs with local configuration and zero after decimal point
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let point = Locale.current.decimalSeparator!
let decSep = Locale.current.groupingSeparator!
let text = textField.text!
let textRange = Range(range, in: text)!
var fractionLength = 0
var isRangeUpperPoint = false
if let startPoint = text.lastIndex(of: point.first!) {
let end = text.endIndex
let str = String(text[startPoint..<end])
fractionLength = str.count
isRangeUpperPoint = textRange.lowerBound >= startPoint
}
if fractionLength == 3 && string != "" && isRangeUpperPoint {
return false
}
let r = (textField.text! as NSString).range(of: point).location < range.location
if (string == "0" || string == "") && r {
return true
}
// First check whether the replacement string's numeric...
let cs = NSCharacterSet(charactersIn: "0123456789\(point)").inverted
let filtered = string.components(separatedBy: cs)
let component = filtered.joined(separator: "")
let isNumeric = string == component
if isNumeric {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.maximumFractionDigits = 2
// Combine the new text with the old; then remove any
// commas from the textField before formatting
let newString = text.replacingCharacters(in: textRange, with: string)
let numberWithOutCommas = newString.replacingOccurrences(of: decSep, with: "")
let number = formatter.number(from: numberWithOutCommas)
if number != nil {
var formattedString = formatter.string(from: number!)
// If the last entry was a decimal or a zero after a decimal,
// re-add it here because the formatter will naturally remove
// it.
if string == point && range.location == textField.text?.count {
formattedString = formattedString?.appending(point)
}
textField.text = formattedString
} else {
textField.text = nil
}
}
return false
}