Search code examples
swiftnumber-formattingnsnumberformatter

Convert string into currency number format, with, without or with 0 decimal places


I have a string that always converted into something like this where the user inputs a number and always starting in the decimal places,

So I have 0.01 -> 0.10 -> 1.00

but I don't want something like that, I want to convert only what the user has typed

here's my existing code that convert 100000 into 1,000.00

func convertme(string: String) -> String{
    var number: NSNumber!
    let formatter = NumberFormatter()
    formatter.numberStyle = .currencyAccounting
    formatter.currencySymbol = ""
    formatter.maximumFractionDigits = 2
    formatter.minimumFractionDigits = 2

    var amountWithPrefix = string

    // 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, string.count), withTemplate: "")
    
    print("amountWithPrefix", amountWithPrefix)

    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)!
}

expected result:

I want to to format the number on the string without adding additional data, I want to turn (100000. into 100,000.) (100000.0 into 100,000.0

I want my 100000 be converted into 100,000, and only going to have a decimal if the user inputed a decimal too, so when the user inputted 100000.00 it will be converted into 100,000.00.

PS. I have a regex there that accepts only number but not the decimal, how can I make it also accept decimal?


Solution

  • You can simply filter non digits or periods from the original string, try to coerce the resulting string to integer. If successful set the formatter maximum fraction digits to zero otherwise set the maximum fraction digits to 2 and coerce the string to double:

    extension Formatter {
        static let currency: NumberFormatter = {
            let formatter = NumberFormatter()
            formatter.locale = .init(identifier: "en_US_POSIX")
            formatter.numberStyle = .currencyAccounting
            formatter.currencySymbol = ""
            return formatter
        }()
    }
    

    extension Numeric {
        var currencyUS: String {
             Formatter.currency.string(for: self) ?? ""
        }
    }
    

    func convertme(string: String) -> String {
        let string = string.filter("0123456789.".contains)
        if let integer = Int(string)  {
            Formatter.currency.maximumFractionDigits = 0
            return Formatter.currency.string(for: integer) ?? "0"
        }
        Formatter.currency.maximumFractionDigits = 2
        return Double(string)?.currencyUS ?? "0"
    }
    

    convertme(string: "100000")     // "100,000"
    convertme(string: "100000.00")  // "100,000.00"
    


    edit/update: "100,000." it is not a valid number format. You would need to manually insert your period at the end of the string.

    func convertme(string: String) -> String {
        var string = string.filter("0123456789.".contains)
        // this makes sure there is only one period and keep only the last one in the string
        while let firstIndex = string.firstIndex(of: "."),
              let _ = string[firstIndex...].dropFirst().firstIndex(of: ".") {
            string.remove(at: firstIndex)
        }
        // get the index of the period in your string
        if let index = string.firstIndex(of: ".") {
            // get the fraction digits count and set the number formatter appropriately
            let fractionDigits = string[index...].dropFirst().count
            Formatter.currency.minimumFractionDigits = fractionDigits
            Formatter.currency.maximumFractionDigits = fractionDigits
            // Number Formatter wont add a period at the end of the string if there is no fractional digits then you need to manually add it yourself
            if fractionDigits == 0 {
                return (Double(string)?.currencyUS ?? "0") + "."
            }
        } else {
            // in case there is no period set the fraction digits to zero
            Formatter.currency.minimumFractionDigits = 0
            Formatter.currency.maximumFractionDigits = 0
        }
        return Double(string)?.currencyUS ?? "0"
    }
    

    Playground Testing:

    convertme(string: "100000")      // "100,000"
    convertme(string: "100000.")     // "100,000."
    convertme(string: "100000.0")    // "100,000.0"
    convertme(string: "100000.00")   // "100,000.00"
    convertme(string: "100000.000")  // "100,000.000"