Dynamically format a number to have commas in a UITextField - ios

How to format the price textfield text as a currency format like 234,345,567 and also I need to restrict the decimal points to not more than 2 and also to append $ symbol when user starts typing.
for example : $234,345,678.25
like this I want to add comma between 3numbers when they type the amount in textfield
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
if ((string == "0" || string == "") && (txtFldPostalCode.text! as NSString).range(of: ".").location < range.location) {
return true
}
let cs = NSCharacterSet(charactersIn: "0123456789.").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 = 8
let newString = (textField.text! as NSString).replacingCharacters(in: range, with: string)
let numberWithOutCommas = newString.replacingOccurrences(of: ",", with: "")
let number = formatter.number(from: numberWithOutCommas)
if number != nil {
var formattedString = formatter.string(from: number!)
if string == "." && range.location == textField.text?.length {
formattedString = formattedString?.appending(".")
}
textField.text = formattedString
} else {
textField.text = nil
}
}
return false
}

For Swift 3. Input currency format on a text field (from right to left)
#IBOutlet weak var textfield: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
textfield.addTarget(self, action: #selector(myTextFieldDidChange), for: .editingChanged)
// Do any additional setup after loading the view.
}
#objc func myTextFieldDidChange(_ textField: UITextField) {
if let amountString = textField.text?.currencyInputFormatting() {
textField.text = amountString
}
}
}
extension String {
// formatting text for currency textField
func currencyInputFormatting() -> String {
var number: NSNumber!
let formatter = NumberFormatter()
formatter.numberStyle = .currencyAccounting
formatter.currencySymbol = "$"
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
var amountWithPrefix = self
let regex = try! NSRegularExpression(pattern: "[^0-9]", options: .caseInsensitive)
amountWithPrefix = regex.stringByReplacingMatches(in: amountWithPrefix, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSMakeRange(0, self.characters.count), withTemplate: "")
let double = (amountWithPrefix as NSString).doubleValue
number = NSNumber(value: (double / 100))
guard number != 0 as NSNumber else {
return ""
}
return formatter.string(from: number)!
}
Hope this is helpful...

Related

Formatting Currency - Swift

I am looking for a textfield currency formatter such that it fulfils the following criterias:
It should be formatted(comma separated) as I am typing
10 digits before decimal point and 2 digits after it, should be allowed
It should allow a regex for (2)
When we cut, cursor should remain at the same place
When we type in the middle of the currency, cursor should not shift to left.
It should support localization (Commas and Periods) in regex.
I have tried alot of solutions:
Using NSCharacterSet (This is the closest but regex fails here due to interchange of . and , during localization, also we have used .decimal type here to avoid the $ in textField)
class func checkTextField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let textBeforeEditing = textField.text else {
return true
}
if ((string == "0" || string == "") && (textField.text! as NSString).range(of: ".").location < range.location) {
return true
}
var currentPosition = 0
if let selectedRange = textField.selectedTextRange {
currentPosition = textField.offset(from: textField.beginningOfDocument, to: string == "" ? selectedRange.end : selectedRange.start)
}
let allowedCharacterSet = NSCharacterSet(charactersIn: "0123456789.").inverted
let filtered = string.components(separatedBy: allowedCharacterSet)
let component = filtered.joined(separator: "")
let isNumeric = string.replacingOccurrences(of: ",", with: "") == component
var textFieldString : String = ""
var numberWithoutCommas : String = ""
guard isNumeric else {
return false
}
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
textFieldString = (textField.text! as NSString).replacingCharacters(in: range, with: string)
numberWithoutCommas = textFieldString.replacingOccurrences(of: ",", with: "")
let formattedNumberWithoutCommas = formatter.number(from: numberWithoutCommas)
guard let formattedNumber = formattedNumberWithoutCommas, var formattedString = formatter.string(from: formattedNumber) else {
textField.text = nil
return false
}
if string == "." && range.location == textField.text?.count {
formattedString = formattedString.appending(".")
}
textField.text = formattedString
currentPosition = getCursorPositionForTextField(string: string, cursorPosition: currentPosition, formattedString: formattedString, textBeforeEditing: textBeforeEditing)
handleTextFieldCursor(cursorPosition: currentPosition, textField: textField)
return false
}
Using NumberFormatter but cursor shifts to end on every cut/paste
extension String {
func currencyInputFormatting() -> String {
var number: NSNumber!
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
var amountWithPrefix = self
// remove from String: "$", ".", ","
let regex = try! NSRegularExpression(pattern: "[^0-9]", options: .caseInsensitive)
amountWithPrefix = regex.stringByReplacingMatches(in: amountWithPrefix, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSMakeRange(0, self.characters.count), withTemplate: "")
let double = (amountWithPrefix as NSString).doubleValue
number = NSNumber(value: (double / 100))
guard number != 0 as NSNumber else {
return ""
}
return formatter.string(from: number)!
}
}
I have spent almost a day or two finding a 100% workable solution but not able to resolve.
Any help will be appreciated
EDIT
I have come quite close to the solution with the help of the #denis_lor answer but still unable to achieve the interchange of comma with period. Here's my updated code, am I missing something? It works fine with english but not with spanish.
class func checkTextField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
guard let textBeforeEditing = textField.text else {
return true
}
if ((string == "0" || string == "") && (textField.text! as NSString).range(of: "\(NSLocalizedString("core_decimal_separator_symbol", comment: ""))").location < range.location) {
return true
}
var currentPosition = 0
if let selectedRange = textField.selectedTextRange {
currentPosition = textField.offset(from: textField.beginningOfDocument, to: string == "" ? selectedRange.end : selectedRange.start)
}
let allowedCharacterSet = NSCharacterSet(charactersIn: "0123456789\(NSLocalizedString("core_decimal_separator_symbol", comment: ""))").inverted
let filtered = string.components(separatedBy: allowedCharacterSet)
let component = filtered.joined(separator: "")
let isNumeric = string.replacingOccurrences(of: NSLocalizedString("core_thousand_separator_symbol", comment: ""), with: "") == component
var textFieldString : String = ""
var numberWithoutCommas : String = ""
guard isNumeric else {
return false
}
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
textFieldString = (textField.text! as NSString).replacingCharacters(in: range, with: string)
numberWithoutCommas = textFieldString.replacingOccurrences(of: NSLocalizedString("core_thousand_separator_symbol", comment: ""), with: "")
let formattedNumberWithoutCommas = formatter.number(from: numberWithoutCommas)
guard let formattedNumber = formattedNumberWithoutCommas, var formattedString = formatter.string(from: formattedNumber) else {
textField.text = nil
return false
}
if string == NSLocalizedString("core_decimal_separator_symbol", comment: "") && range.location == textField.text?.count {
formattedString = formattedString.appending(NSLocalizedString("core_decimal_separator_symbol", comment: ""))
}
textField.text = formattedString
currentPosition = getCursorPositionForTextField(string: string, cursorPosition: currentPosition, formattedString: formattedString, textBeforeEditing: textBeforeEditing)
handleTextFieldCursor(cursorPosition: currentPosition, textField: textField)
return false
}
Ok so it looks your concern here could be solved by making a first round implementation of your first solution, where you only need to think about localization of , and .. That is easy, you could implement it in many different ways, but the important part is you have your app for example localized in let's say two language that treats decimals and thousands with different symbols (let's assume as an example those languages are english and italian):
[en] language treats the separation of decimals with a , and thousands with a .
[it] language treats the separation of decimals with a . and thousands with a ,
A) What you could do is to create a Localizable.strings file and then localize your project in let's say english and italian as an example. To do it add the language here.
B) Then go to your Localizable.strings file and localize it for the languages you support (English and Italian as an example), like in this image that was done for German and English
You will end up with two Localizable.strings now, one for English and one for Italian:
Localizable.strings (English)
core_decimal_separator_symbol = ",";
core_thousand_separator_symbol = ".";
Localizable.strings (Italian)
core_decimal_separator_symbol = ".";
core_thousand_separator_symbol = ",";
C) And in your code, everywhere you need to address, for example, your decimal separator symbol, instead of writing it hard coded you could do something like:
removeDecimalSeparator = numberAsString.replacingOccurrences(of: NSLocalizedString("core_decimal_separator_symbol", comment: ""), with: "")
So whenever your app is localized to English for example this code will traslate into:
removeDecimalSeparator = numberAsString.replacingOccurrences(of: ",", with: "")
And when your app is localized to Italian for example this code will traslate into:
removeDecimalSeparator = numberAsString.replacingOccurrences(of: ".", with: "")
To conclude: consider these as example taking into account the Localizable.strings we have in this answer. Just to show you how you could manipulate some symbols in different ways for different languages by using Localization in your app.

Detect Cut operation in textfield - Swift

I have implemented the shouldChangeCharactersIn textfield delegate method. I have formatted the value in textfield so that textfield string becomes a comma separated currency type value. For that, I am explicitly handling the cursor to support conditions like inserting 2 commas at a time. The code used is :
let textBeforeEditing = textField.text!
if ((string == "0" || string == "") && (textField.text! as NSString).range(of: ".").location < range.location) {
return true
}
var currentPosition = 0
if let selectedRange = textField.selectedTextRange {
currentPosition = textField.offset(from: textField.beginningOfDocument, to: string == "" ? selectedRange.end : selectedRange.start)
}
let allowedCharacterSet = NSCharacterSet(charactersIn: "0123456789.").inverted
let filtered = string.components(separatedBy: allowedCharacterSet)
let component = filtered.joined(separator: "")
let isNumeric = string.replacingOccurrences(of: ",", with: "") == component
var textFieldString : String = ""
var numberWithoutCommas : String = ""
guard isNumeric else {
return false
}
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
textFieldString = (textField.text! as NSString).replacingCharacters(in: range, with: string)
numberWithoutCommas = textFieldString.replacingOccurrences(of: ",", with: "")
let formattedNumberWithoutCommas = formatter.number(from: numberWithoutCommas)
guard let formattedNumber = formattedNumberWithoutCommas, var formattedString = formatter.string(from: formattedNumber) else {
textField.text = nil
return false
}
if string == "." && range.location == textField.text?.count {
formattedString = formattedString.appending(".")
}
var filteredRegex = [NSTextCheckingResult]()
filteredRegex = Constant.Regex.decimalRegex.matches(in: formattedString, options: [], range: NSMakeRange(0, formattedString.count))
guard filteredRegex.count == 1 else {
return false
}
textField.text = formattedString
currentPosition = getCursorPositionForTextField(string: string, cursorPosition: currentPosition, formattedString: formattedString, textBeforeEditing: textBeforeEditing)
handleTextFieldCursor(cursorPosition: currentPosition, textField: textField)
return false
So when I type 1234567890, it gets formatted to 1,234,567,890
The issue here is that when I cut any characters from textfield, I am unable to paste it anywhere else. I am unable to detect the reason why it isn't getting pasted. Please could someone help me out.
Is there any way to detect the cut operation for the same? Cut operation is getting overridden by the above code it seems.

UITextField Currency Format Left to Right

I want to format my UITextField with having a $ on the left when I enter an amount.
So far what my code does is when I enter let says $5.65 this is how it's entered: $0.05 -> $0.56 -> $5.65
I want it so that it's not right to left but left to right so something like this: $5 -> $5. -> $5.6 -> $5.65
But I want to restrict it to only two decimals place, the dollar sign is on the left, and you can not type any other characters (e.g: !,#,#,$,%,^, A-Z')
This is what I have currently:
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let text: NSString = (textField.text ?? "") as NSString
let finalString = text.replacingCharacters(in: range, with: string)
// 'currency' is a String extension that doews all the number styling
amuTextField.text = finalString.currency
// returning 'false' so that textfield will not be updated here, instead from styling extension
return false
}
func currencyInputFormatting() -> String {
var number: NSNumber!
let formatter = NumberFormatter()
formatter.numberStyle = .currencyAccounting
formatter.currencySymbol = "$"
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
var amountWithPrefix = self
// remove from String: "$", ".", ","
let regex = try! NSRegularExpression(pattern: "[^0-9]", options: .caseInsensitive)
amountWithPrefix = regex.stringByReplacingMatches(in: amountWithPrefix, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSMakeRange(0, self.characters.count), withTemplate: "")
let double = (amountWithPrefix as NSString).doubleValue
number = NSNumber(value: (double / 100))
return formatter.string(from: number)!
}
You could use this to limit the decimal places after the .:
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.firstIndex(of: ".") {
numberOfDecimalDigits = newText.distance(from: dotIndex, to: newText.endIndex) - 1
} else {
numberOfDecimalDigits = 0
}
return isNumeric && numberOfDots <= 1 && numberOfDecimalDigits <= 2
}

how to put a $ coin mask in alert.addTextfield?

how to put a $ coin mask in alert.addTextfield?
currency ?
with textfield to do with?
https://code.i-harness.com/en/q/1c673c6
func showalert(with marcas: Marcas?){
let title = marcas == nil ? "Adicionar" : "Editar"
let alert = UIAlertController(title: title + " Marca", message: nil, preferredStyle: .alert)
alert.addTextField { (textField) in textField.placeholder = "Nome da Marca"
if let name = marcas?.nome {
textField.text = name
}
}
alert.addTextField { (textFieldValor) in textFieldValor.placeholder = "Preço"
if let valor = marcas?.valor {
textFieldValor.text = valor
}
}
textFieldValor.addTarget(self, action: #selector(self.myTextFieldDidChange), for: .editingChanged)
#objc func myTextFieldDidChange(_ textField: UITextField) {
if let amountString = textField.text?.currencyInputFormatting() {
textField.text = amountString
}
}
extension String {
// formatting text for currency textField
func currencyInputFormatting() -> String {
var number: NSNumber!
let formatter = NumberFormatter()
formatter.numberStyle = .currencyAccounting
formatter.currencySymbol = "R$"
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
var amountWithPrefix = self
// remove from String: "$", ".", ","
let regex = try! NSRegularExpression(pattern: "[^0-9]", options: .caseInsensitive)
amountWithPrefix = regex.stringByReplacingMatches(in: amountWithPrefix, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSMakeRange(0, self.characters.count), withTemplate: "")
let double = (amountWithPrefix as NSString).doubleValue
number = NSNumber(value: (double / 100))
// if first number is 0 or all numbers were deleted
guard number != 0 as NSNumber else {
return ""
}
return formatter.string(from: number)!
}
}

Percentage mask for textfield in Swift

Ive been trying to achieve a percentage mask for a textfield that starts with the value 0.00%. If a user taps '1' it will become 0.01%. If they then tap 0 it becomes 0.10% and so on.
I've achieved this already with currency. And I've come very close using percentage but not managed to create exactly what I'm after.
So, field is originally set to 0.00%. I'd like to display the percentage symbol, I'd like the max number to be 100.00%, I'd like the max decimal places to be 2, and like the field to be updated when the user types and I'd like them to be able to press backspace to delete the last number entered. Oh and also when it reaches the maximum of 100.00, pressing another number does not reset the value, or do anything screwy.
Here is the code I've tried. I've also tried MANY variations:
func textFieldDidChanged(textField: UITextField) {
numberFormatter.numberStyle = .PercentStyle
numberFormatter.maximumFractionDigits = 2
numberFormatter.minimumFractionDigits = 2
numberFormatter.maximum = 100
let text = textField.text!.stringByReplacingOccurrencesOfString(numberFormatter.percentSymbol, withString: "").stringByReplacingOccurrencesOfString(numberFormatter.groupingSeparator, withString: "").stringByReplacingOccurrencesOfString(numberFormatter.decimalSeparator, withString: "")
textField.text = numberFormatter.stringFromNumber((text as NSString).doubleValue / 100.0)
}
I've looked all over, and spent way too long on this. Please help.
This is what I came up with (and for my application, its about as much effort I want to give). Two cases need to be handled: 1) inputting and 2) deleting. Handling the input was the easier portion (modified from a previous post). Handling the delete was trickier to realize. Basically when backspace is detected, move right one decimal place.
Handles input:
mInputRate.addTarget(self, action: #selector(rateTextFieldChanged), for: .editingChanged)
...
#objc func rateTextFieldChanged (_ textField : UITextField){
if let amountString = textField.text?.rateInputFormatting() {
textField.text = amountString
calculate()
}
}
and
extension String {
// formatting text for currency textField
func rateInputFormatting() -> String {
var number: NSNumber!
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.maximumFractionDigits = 3
formatter.minimumFractionDigits = 3
formatter.maximum = 100
formatter.minimum = 0
var amountWithPrefix = self
// remove from String: "$", ".", ","
let regex = try! NSRegularExpression(pattern: "[^0-9]", options: .caseInsensitive)
amountWithPrefix = regex.stringByReplacingMatches(in: amountWithPrefix, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSMakeRange(0, self.characters.count), withTemplate: "")
let double = (amountWithPrefix as NSString).doubleValue
number = NSNumber(value: (double / 1000))
// if first number is 0 or all numbers were deleted
guard number != 0 as NSNumber else {
return ""
}
return "\(formatter.string(from: number)!)%"
}
}
This handles delete (backspace):
extension CalculatorVC : UITextFieldDelegate{
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let char = string.cString(using: String.Encoding.utf8)!
let isBackSpace = strcmp(char, "\\b")
if (isBackSpace == -92) {
//delete the second to last character
let text = textField.text!
let textDouble = text.replacingOccurrences(of: "%", with: "")
let doubleAdjusted = Double(textDouble)! / 10.0
//pad to three decimal places
let formatter = NumberFormatter()
formatter.maximumFractionDigits = 3
formatter.minimumFractionDigits = 3
formatter.roundingMode = .down
let num = NSNumber(value: doubleAdjusted)
let newText = formatter.string(from: num)!
textField.text = "\(newText)%"
}
return true
}
}

Resources