I'm working on a clientside iOS app for my school that involves students purchasing things, and I wrote a String
extension for formatting a String
into USD. Is there a way to improve this or make it more "swifty"?
extension String {
var usdFormat: String {
var str = self
var postDec = str.components(separatedBy: ".")
switch postDec[1].count {
case 0: str.append("00")
case 1: str.append("0")
default: break
}
str = "$" + str
return str
}
}
2 Answers 2
... for formatting a String into USD ...
A string is not the appropriate type to hold a monetary amount. What would your
extension method return if called on the string "abc"
or "?π€¬"
? A price is a number
and should be stored as such. Possible choices are
- An integer holding the price in units of the smallest denomination (e.g. cents), or
- A
Decimal
which represents a base-10 number with up do 38 decimal digits.
Creating a textual representation of a number is the job for a
NumberFormatter
.
It has options to specify the desired number of fractional digits, and even
has a dedicated .currency
style.
Using a number formatter has several advantages over "manually" formatting the price, such as:
- It chooses the appropriate style according to the user's locale.
- It puts the currency symbol at the correct position.
- It rounds the number to the correct number of decimal places.
Formatting a US dollar currency value is as simple as
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.currencyCode = "USD"
let usPrice: Decimal = 1234.56
let displayString = formatter.string(for: usPrice)!
print(displayString)
This produces the string 1,234γγ«.56
if the user's locale is "en_US".
On my device (with a german locale) it produces 1.234,56 $
.
I would begin with more descriptive variable names. Something like formattedString
and splitComponents
would work in this case. The postDec
or splitComponents
variable is never mutated so that can be declared as a let.
extension String {
var usdFormatted: String {
var formattedString = self
let splitComponents = self.components(separatedBy: ".")
}
}
Next I would want to address a couple issues with the extension. The first glaring issue is that if you call this on a value that isn't numeric or separated by a period, it will crash. For this, I would recommend a guard
statement before we begin doing any processing. This will provide a quick exit when a value is found that we don't want to use.
extension String {
var usdFormatted: String {
var formattedString = self
guard let currency = Double(self) else {
return formattedString
}
let splitComponents = self.components(separatedBy: ".")
}
}
Now that we have addressed cases like ABC
and 14FOO
, we can look forward to handling the other logic that you have provided.
This also provides an interesting point where we now have a floating point value to work with. We can now take a shortcut and work directly with that floating point value. We can convert it to a string and specify the level of accuracy that we would like from that value. This can be done using String(format: "%.2f", currency)
. The format string signifies that we are providing a floating point value %f
, and by passing in the .2
we can specify the precision for the string.
That will leave us with this:
extension String {
var usdFormatted: String {
var formattedString = self
guard let currency = Double(self) else {
return formattedString
}
formattedString = "$" + String(format: "%.2f", currency)
return formattedString
}
}
A little bit of cleanup can be done since formattedString
is unnecessary at this point, so that can be removed and you would end up with this extension:
extension String {
var usdFormatted: String {
guard let currency = Double(self) else {
return self
}
return "$" + String(format: "%.2f", currency)
}
}
The only thing to note that would differ from this change is that it would provide rounding. If you did not want rounding to round up for any reason in this, you could accomplish this by adjusting the return statement like this return "$" + String(format: "%.2f", currency.rounded(.down))
.
-
\$\begingroup\$ I think that using a
Decimal
may be more appropriate than aDouble
as the intermediate representation. \$\endgroup\$200_success– 200_success2018εΉ΄10ζ02ζ₯ 20:52:23 +00:00Commented Oct 2, 2018 at 20:52
Explore related questions
See similar questions with these tags.