Extracting currency value from a formatted string in Swift - swift

I have the following string, from which I want to remove the currency formatting and extract the numeric value for manipulation:
"Product Price":"\u00a3314.95",
I've tried using the following code:
let productvalue = model[indexPath.row].productPrice ?? ""
let prodval = productvalue.replacingOccurrences(of: "\u00a3", with: "")
let proqty = model[indexPath.row].quantity ?? ""
let totalprice = (Int(prodval)) * (Int(proqty))
However, when I run this code, I am getting the following response from an API:
Binary operator '*' cannot be applied to two 'Int?' operands

The problem is that the init methods you use returns an optional value so you need to include a value in case the conversion from String to Int returns nil like
let value = Int(someString) ?? 0
but in your case you are dealing with decimal values so you need to convert to Double
let totalprice = (Double(prodval) ?? 0.0) * (Double(proqty) ?? 0.0)

You can use this extension that will help you remove currency symbols from the amount.
Replace your desired currency with $ for example use € instead of unicode.
extension String {
func removeFormatAmount() -> Double {
let formatter = NumberFormatter()
formatter.locale = Locale(identifier: "en_US")
formatter.numberStyle = .currency
formatter.currencySymbol = "$"
formatter.decimalSeparator = ","
return formatter.number(from: self) as Double? ?? 0
}
}
How to use this extension.
let currencyString = "$1,000.00"
let amount = currencyString.removeFormatAmount() // 1000.0

You can convert currency string to decimal
let str = "£300"
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.currencyCode = "GBP"
if let number = formatter.number(from: str) {
let amount = number.decimalValue
print(amount)
}

If you look closely the constructors of integers from string return optional values. So what you have is:
let totalprice = {
let a: Int? = Int(prodval)
let b: Int? = Int(proqty)
return a*b // Error
}()
a quick fix is to force-unwrap it using ! resulting in let totalprice = (Int(prodval))! * (Int(proqty))! but I would not suggest it because it may crash your app.
A but more elegant solution is to use defaults:
let totalprice = {
let a: Int = Int(prodval) ?? 0
let b: Int = Int(proqty) ?? 0
return a*b
}()
But on the other hand why are you even using integers here? What if the price is not a whole number? I suggest you rather use decimal numbers to handle these cases:
let a = NSDecimalNumber(string: prodval)
let b = NSDecimalNumber(string: proqty)
let totalprice = a.multiplying(by: b)
This is now working with decimal numbers directly. To get a double value or integer value you would simply need to use it's properties totalprice.doubleValue or totalprice.intValue. But there is no need for that either. If you need to convert it back to string simply use formatters:
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.currencySymbol = "$"
let finalOutput: String = formatter.string(from: totalprice)
There are many possible solutions to this and if possible I would try to do it all with formatters and decimal numbers. For instance in your case something like the following might do the trick:
private func generateFormatter(currencySymbol: String = "$", decimalSeparator: String = ".") -> NumberFormatter {
let formatter = NumberFormatter()
formatter.currencySymbol = currencySymbol
formatter.decimalSeparator = decimalSeparator
formatter.numberStyle = .currency
return formatter
}
private func parseValue(_ input: String?, formatterInfo: (currencySymbol: String, decimalSeparator: String)) -> NSNumber? {
guard let input = input else { return nil }
let formatter = generateFormatter(currencySymbol: formatterInfo.currencySymbol, decimalSeparator: formatterInfo.decimalSeparator)
return formatter.number(from: input)
}
private func multiplyValues(_ values: [String?], formatterInfo: (currencySymbol: String, decimalSeparator: String)) throws -> NSNumber {
return try values.reduce(NSDecimalNumber(value: 1.0)) { result, value in
guard let parsedValue = parseValue(value, formatterInfo: formatterInfo) else {
throw NSError(domain: "Parsing values", code: 400, userInfo: ["dev_message": "Could not parse a value \(value ?? "[Null value]")"])
}
return NSDecimalNumber(decimal: result.decimalValue).multiplying(by: NSDecimalNumber(decimal: parsedValue.decimalValue))
}
}
let values = ["$1.2", "$1.6", "$2"]
let result = try? multiplyValues(values, formatterInfo: ("$", "."))
let parsedResult: String = {
guard let result = try? multiplyValues(values, formatterInfo: ("$", ".")) else { return "Could not produce result" }
return generateFormatter(currencySymbol: "$", decimalSeparator: ".").string(from: result) ?? "Could not format result"
}()
print(result ?? "No result")
print(parsedResult)
I hope the code speaks for itself and you can see it is easy to change/inject different formats/symbols.

Related

Converting String to Double/Float loses precision for large numbers in Swift 5

When i convert "0.0000335651599321165" String value to Double
let value = "0.0000335651599321165"
var doubleValue = Double(value)
It returns me the value 3.36597214193249e-05.
How can i get the original number in swift 5.
If you want to keep your floating precision you need to use Decimal type and make sure to use its string initializer:
let value = "0.0000335651599321165"
if let decimal = Decimal(string: value) {
print(decimal)
}
This will print:
0.0000335651599321165
edit/update:
When displaying your value to the user with a fixed number of fraction digits you can use Number Formatter and you can choose a rounding mode as well:
extension Formatter {
static let number = NumberFormatter()
}
extension Numeric {
func fractionDigits(min: Int = 6, max: Int = 6, roundingMode: NumberFormatter.RoundingMode = .halfEven) -> String {
Formatter.number.minimumFractionDigits = min
Formatter.number.maximumFractionDigits = max
Formatter.number.roundingMode = roundingMode
Formatter.number.numberStyle = .decimal
return Formatter.number.string(for: self) ?? ""
}
}
let value = "0.0000335651599321165"
if let decimal = Decimal(string: value) {
print(decimal.fractionDigits()) // "0.000034\n"
}

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?
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"

Swift - Reading different types of values from a dictionary

Presently the code that I have is reading all the values as String. However at times when an integer or decimal values are present, it gets read as nil.
Present code:
let fieldName = String(arr[0])
var res = dict[fieldName.uppercased()] as? String
if res == nil {
res = dict[fieldName.lowercased()] as? String
}
url = url.replacingOccurrences(of: testString, with: res?.addingPercentEncoding(withAllowedCharacters: allowedCharSet) ?? "")
There are times when "dict[fieldName.uppercased()]" returns value such as 3 or 40.4, but value in my res object is nil since I am expecting a string.
How can I get read different types of values and update the occurrences in my url?
Code that I tried:
let fieldName = String(arr[0])
var res = dict[fieldName.uppercased()] as? AnyObject
if res == nil {
res = dict[fieldName.lowercased()] as? AnyObject
}
url = url.replacingOccurrences(of: testString, with: res?.addingPercentEncoding(withAllowedCharacters: allowedCharSet) ?? "")
With this I am getting errors while replacing the occurrences since "addingPercentEncoding" only works on String.
So I check the class of res object and if it is not String, I try doing the below, but getting error since res is of type AnyObject and if that's not present, I try to replace it with empty string.
url = url.replacingOccurrences(of: testString, with: res ?? "" as String)
There is a common type of String, Int and Double: CustomStringConvertible
Conditional downcast the value to CustomStringConvertible and get a string with String Interpolation
let fieldName = String(arr[0])
if let stringConvertible = dict[fieldName.uppercased()] as? CustomStringConvertible {
url = url.replacingOccurrences(of: testString, with: "\(stringConvertible)".addingPercentEncoding(withAllowedCharacters: allowedCharSet
}
You should separate the "get the dictionary value" part and the "convert it to my desired type" part. And you should check the type of the value you got from the dictionary using if let statements.
let value = dict[fieldName.uppercased()] ?? dict[fieldName.lowercased()]
if let string = value as? String {
url = url.replacingOccurrences(of: testString, with: string.addingPercentEncoding(withAllowedCharacters: allowedCharSet) ?? "")
} else if let double = value as? Double {
url = url.replacingOccurrences(of: testString, with: "\(double)".addingPercentEncoding(withAllowedCharacters: allowedCharSet) ?? "")
} else if let integer = value as? Int {
url = url.replacingOccurrences(of: testString, with: "\(integer)".addingPercentEncoding(withAllowedCharacters: allowedCharSet) ?? "")
} else {
// value is nil, or is none of the types above. You decide what you want to do here
}

How to sum NSNumber array in Swift?

How to sum NSNumber array in Swift? I'm getting this error when I use .reduce. any other way to sum the NSNumber array?
Cannot invoke 'reduce' with an argument list of type '(Int, _)
var record = [Record]()
var incomeFilter: [Record] = []
let formatter = NumberFormatter()
formatter.numberStyle = .currencyAccounting
formatter.currencySymbol = "¥"
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
incomeFilter = record.filter { $0.recordtype!.contains("Income") && $0.createdAt! == recordItem.createdAt!}
let incomeSumArray = incomeFilter.map{formatter.number(from: $0.amount!) ?? 0.00}.reduce(0, +)
//Cannot invoke 'reduce' with an argument list of type '(Int, _)
print(incomeSumArray)
//result without .reduce
//[9.99, 6.58, 7777.77]
I think the problem is using ?? 0 with an optional of type NSNumber. You always need the same type when nil-coalescing. You have to convert NSNumber to a numeric value (double) first:
let incomeSum = incomeFilter
.map {
formatter.number(from: $0.amount!)?.doubleValue ?? 0
}
.reduce(0, +)
or
let incomeSum = incomeFilter
.map {
guard
let amountString = $0.amount,
let number = formatter.number(from: amountString)
else {
return 0
}
return number.doubleValue
}
.reduce(0, +)
You could map the incomeFilter to be an array of doubles (incomeFilter.map{formatter.number(from: $0.amount!) ?? 0.00}) before reducing it:
let incomeSumArray = incomeFilter.map {
formatter.number(from: $0.amount!) ?? 0.00
}.map {
$0.doubleValue
}.reduce(0, +)

Get a Decimal from a String

I have a problem to get a Decimal here.
I have tried this code but the results is 9.0 , How can i get 0.9 ?
let distances = "0.9 mil"
let stratr = distances.characters.split{$0 == " "}.map(String.init)
for item in stratr {
let components = item.components(separatedBy: NSCharacterSet.decimalDigits.inverted)
let part = components.joined(separator: "")
if let doubVal = Double(part) {
print("this is a number -> \(doubVal)")
}
You can separate the string by the space character and then initialize a Float using the first component.
let str = "0.9 mil"
let decimal = str.components(separatedBy: " ").first.flatMap { Float($0) }
print(decimal) // 0.9
The String struct provides an instance method that can be used to remove characters based on a given CharacterSet. In this case, you can use the letters and whitespaces character sets to isolate your decimal value and then create a Decimal from it.
let distances = "0.9 mil"
let decimal = Decimal(string: distances.trimmingCharacters(in: CharacterSet.letters.union(.whitespaces)))
if let decimal = decimal {
print(decimal) // Prints 0.9
}
extension String {
/// "0.9 mil" => "0.9"
var decimals: String {
return trimmingCharacters(in: CharacterSet.decimalDigits.inverted)
}
/// "0.9" => 0.9
var doubleValue: Double {
return Double(self) ?? 0
}
}
Usage:
let distance = "0.9 mil"
print(distance.decimals) // "0.9"
print(distance.decimals.doubleValue) // 0.9
print(distance.doubleValue) // 0 (because Double("0.9 mil") => nil)
Never mind i find the Answer
let distances = "0.9 mil"
let stratr = distances.characters.split{$0 == " "}.map(String.init)
for item in stratr {
let components = item.components(separatedBy: NSCharacterSet.decimalDigits.inverted)
let part = components.joined(separator: ".")
if let doubVal = Double(part) {
print("this is a number -> \(doubVal)")
}
I think when i set Joined(separator : ".") it will joined the String with an "." as separator