1
\$\begingroup\$

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
 }
}
200_success
145k22 gold badges190 silver badges478 bronze badges
asked Oct 2, 2018 at 8:33
\$\endgroup\$

2 Answers 2

2
\$\begingroup\$

... 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 $.

answered Oct 3, 2018 at 11:12
\$\endgroup\$
0
\$\begingroup\$

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)).

answered Oct 2, 2018 at 18:28
\$\endgroup\$
1
  • \$\begingroup\$ I think that using a Decimal may be more appropriate than a Double as the intermediate representation. \$\endgroup\$ Commented Oct 2, 2018 at 20:52

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.