1

I am looking for a textfield currency formatter such that it fulfils the following criterias:

  1. It should be formatted(comma separated) as I am typing
  2. 10 digits before decimal point and 2 digits after it, should be allowed
  3. It should allow a regex for (2)
  4. When we cut, cursor should remain at the same place
  5. When we type in the middle of the currency, cursor should not shift to left.
  6. It should support localization (Commas and Periods) in regex.

I have tried alot of solutions:

  1. 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
    }
    
  2. 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
}
Mamta
  • 921
  • 1
  • 10
  • 28
  • definitely you have to your code for above requirements. but here are few useful links 1. https://stackoverflow.com/questions/15217124/trying-to-format-a-uitextfield-to-behave-like-a-currency-calculator-for-only-num 2. https://www.reddit.com/r/swift/comments/6d4krx/asyoutype_uitextfield_currency_formatting_library/ – Gagan_iOS Apr 18 '19 at 11:58
  • Please post code instead of pictures, unless you want to show some warnings with the code from the compiler. – denis_lor Apr 18 '19 at 11:59
  • 1
    @denis_lor Please check – Mamta Apr 18 '19 at 12:29

1 Answers1

1

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. enter image description 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 enter image description here

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.

denis_lor
  • 6,212
  • 4
  • 31
  • 55
  • 1
    Thanks, will try it and get back shortly – Mamta Apr 19 '19 at 05:49
  • What do you mean it does not work for spanish? What is the value of the localized string core_decimal_separator_symbol once it reaches that code when using spanish language in the iOS Device? Put a breakpoint or do a print(NSLocalized.....) to check the value. Make sure you have your app project with both languages (step 1), check you have two Localizable.strings one for english and one for spanish (step 2). Check in spanish you have the symbol value you expect. – denis_lor Apr 19 '19 at 08:34
  • Last thing is: are you trying thus on simulator? If yes, to set the device language to spanish, you have to edit your App Project Scheme and set the language there to spanish then run the project in simulator. Thats the only way to run the simulator in a specific language. – denis_lor Apr 19 '19 at 08:35
  • I have done all of this but i still seem to miss something to get it right in iOS. My localizable strings work fine in spanish and i have also put breakpoints throughout. It works properly in english – Mamta Apr 19 '19 at 09:24
  • If you print out the localized string symbol when you are in spanish do you get the expected value from the NSLocalized... result? If yes then what goes wrong exactly? – denis_lor Apr 19 '19 at 09:38
  • My approach 1 has some glitch due to which regex not stopping user for spanish language. This is the regex `"^((?:.*[0-9]){0,10})?(?:\\,[0-9]{0,2})?$"` – Mamta Apr 22 '19 at 08:27
  • Where do you got this regex? What's the behavior right now and what are your expectations out of that regex? We are going a bit out of this question scope as more issues are coming out. i will suggest to make another question for the regex issue probably and accept this one that dealt with localizing your solutions to allow symbols as decimal dividers such as "," or "." to be swapped through localization in app. – denis_lor Apr 22 '19 at 13:14