I wrote a function in Swift (String extension) to take any currency string, such as "$500.00" or "250,75 €", or even "500" and then return an nsdecimalnumber from it.
Unfortunately, when I pass in "250,75 €" it hits the if (nf.number(from: self) == nil) part of my code and evaluates to nil, thus returning nothing. It only seems to happen when you add change with a comma, for example 250,75. If I did 250 it works, however. It only seems to do this for Euros. USD works just fine ($500.00, 125.75, etc)
Here is my code. Any thoughts? Thank you for your time!
extension String{
func toDecimalFromCurrencyString() -> NSDecimalNumber
{
let nf = NumberFormatter()
nf.numberStyle = NumberFormatter.Style.currency
nf.formatterBehavior = NumberFormatter.Behavior.behavior10_4
nf.generatesDecimalNumbers = true
var test = self.replacingOccurrences(of: nf.currencySymbol!, with: "", options: NSString.CompareOptions.literal, range: nil)
test = test.replacingOccurrences(of: nf.currencyDecimalSeparator!, with: "", options: NSString.CompareOptions.literal, range: nil)
test = test.replacingOccurrences(of: nf.currencyGroupingSeparator!, with: "", options: NSString.CompareOptions.literal, range: nil)
let scanner: Scanner = Scanner(string:test)
let isNumeric = (scanner.scanDecimal(nil) && scanner.isAtEnd)
//If the string entered isn't numeric (for example ... or $$$, then fail)
if (self.trimmingCharacters(in: CharacterSet.whitespaces).utf16.count == 0 || (!isNumeric))
{
return 0.0
}
//If the string contains more than one decimal separator (.), or more than one currency symbol ($), return 0.0
else if ((self.components(separatedBy: nf.currencyDecimalSeparator!).count-1) > 1 || (self.components(separatedBy: nf.currencySymbol!).count - 1) > 1) {
return 0.0
}
//If the string contains more than one consecutive currency grouping separator (i.e. ,,) then return 0.0
else if ((components(separatedBy: nf.currencyGroupingSeparator! + nf.currencyGroupingSeparator!).count-1) > 0)
{
return 0.0
}
else
{
if (nf.number(from: self) == nil) {
return 0.00
}
else {
return nf.number(from: self) as! NSDecimalNumber
}
}
}
}
You can simply trim all non digits from your string and use two number formatters, one with dot and the other with comma and use the nil coalescing operator in chain as follow:
extension Formatter {
static let decimalWithDotSeparator: NumberFormatter = {
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
numberFormatter.decimalSeparator = "."
return numberFormatter
}()
static let decimalWithCommaSeparator: NumberFormatter = {
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
numberFormatter.decimalSeparator = ","
return numberFormatter
}()
}
extension String {
var numberFromCurrency: NSDecimalNumber? {
var string = self
while let first = string.characters.first, Int("\(first)") == nil {
string = String(string.characters.dropFirst())
}
while let last = string.characters.last, Int("\(last)") == nil {
string = String(string.characters.dropLast())
}
return (Formatter.decimalWithDotSeparator.number(from: string) ??
Formatter.decimalWithCommaSeparator.number(from: string) ?? 0)
.decimalValue.decimalNumber
}
}
extension Decimal {
var decimalNumber: NSDecimalNumber { return NSDecimalNumber(decimal: self) }
}
let dollar = "$249.99"
dollar.numberFromCurrency // 249.99
let real = "R$249,99"
real.numberFromCurrency // 249.99
let euro = "0,50€"
euro.numberFromCurrency // 0.5