Second decimal place gets truncated if it is 0 - swift

I'm new to Swift, so bear with me. :)
I'm having trouble showing the output currency to two decimal places. Currently, it only shows one decimal place. For example, if I input $1.10, the output is $1.1.
However, if I input $1.11, the output is still $1.11.
func currencyInputDoubling() -> String {
var number: NSNumber!
let formatter = NumberFormatter()
formatter.numberStyle = .currencyAccounting
formatter.currencySymbol = CurrencyManager.shared.currentCurrency.sign
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
var amountWithPrefix = self
// 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, self.count), withTemplate: "")
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 "\(double / 100)"
}

I would suggest using Locale instead of currencySymbol and to create static number formatters that can be reused.
let currencyFormatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.locale = .current
return formatter
}()
let numberFormatter: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.locale = .current
formatter.minimumFractionDigits = 2
formatter.maximumFractionDigits = 2
return formatter
}()
And then the method can be simplified as
func currencyInputDoubling(_ amountWithPrefix: String) -> String {
guard let value = currencyFormatter.number(from: amountWithPrefix) else { return "" }
return numberFormatter.string(from: value) ?? ""
}
If you need to set Locale to something else than .current you could pass it as an argument
func currencyInputDoubling(_ amountWithPrefix: String, using locale: Locale) -> String {
currencyFormatter.locale = locale
numberFormatter.locale = locale
guard let value = currencyFormatter.number(from: amountWithPrefix) else { return "" }
return numberFormatter.string(from: value) ?? ""
}

I would suggest, at a minimum, using the formatter you created, rather than doing string interpolation. When you return "\(double / 100)", using simple string interpolation, that can’t avail itself of the formatter’s fractional digits setting.
Perhaps:
func currencyInputDoubling() -> String {
let formatter = NumberFormatter()
formatter.numberStyle = .currencyAccounting
formatter.currencySymbol = CurrencyManager.shared.currentCurrency.sign
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
// remove from String: "$", ".", ","
let digitsOnly = filter("0123456789".contains)` // or, if you want to use regex, simply `let digitsOnly = replacingOccurrences(of: "[^0-9]", with: "", options: .regularExpression)`
// return formatted string
guard let value = Double(digitsOnly) {
return ""
}
return formatter.string(for: value / 100) ?? ""
}

Related

Swift number formater currency problems

I want my textField to show 5.000,00 for instance, I manage to limit the characters that my user can type, but my currency isn't working. Also I am new to Swift, how do I get this currency to work out?
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.allowsFloats = true
formatter.currencyDecimalSeparator = ","
formatter.alwaysShowsDecimalSeparator = true
formatter.locale = Locale(identifier: "pt_BR")
formatter.currencyCode = "BRL"
formatter.maximumFractionDigits = 0
let currentText = textField.text ?? ","
guard let stringRange = Range(range, in: currentText) else { return false}
let updatedText = currentText.replacingCharacters(in: stringRange, with: string)
if let groupingSeparator = formatter.groupingSeparator {
if string == groupingSeparator {
return true
}
if let textWithoutGroupingSeparator = textField.text?.replacingOccurrences(of: groupingSeparator, with: "") {
var totalTextWithoutGroupingSeparators = textWithoutGroupingSeparator + string
if string.isEmpty { // pressed Backspace key
totalTextWithoutGroupingSeparators.removeLast()
}
if let numberWithoutGroupingSeparator = formatter.number(from: totalTextWithoutGroupingSeparators),
let formattedText = formatter.string(from: numberWithoutGroupingSeparator) {
textField.text = formattedText
return false
}
return updatedText.count <= 8
}
}
return true
}
I solve this with a string extension, previously I was using a function on my view controller, doing that way solve for me, you just have to change your locale and will works.
extension String {
// formatting text for currency textField
func currencyInputFormatting() -> String {
var number: NSNumber!
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.locale = Locale(identifier: "pt_BR")
formatter.currencySymbol = ""
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
var amountWithPrefix = self
// 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, self.count), withTemplate: "")
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 ""
}
print(formatter.string(from: number))
return formatter.string(from: number)!
}
}

Converting String to Currency Swift

I am having trouble converting my array of String to Currency.
I have created an extension currencyInputFormatting(). However, the commas are being placed in the wrong spots.
Here is my code :-
cell.balanceLabel.text? = (monthlyBalanceStringArray)[indexPath.row].currencyFormatting()
extension String {
// formatting text for currency textField
func currencyFormatting() -> String {
var number: NSNumber!
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.maximumFractionDigits = 2
var amountWithPrefix = self
let regex = try! NSRegularExpression(pattern: "[^0-9]", options: .caseInsensitive)
amountWithPrefix = regex.stringByReplacingMatches(in: amountWithPrefix, options: NSRegularExpression.MatchingOptions(rawValue: 0), range: NSMakeRange(0, self.characters.count), withTemplate: "")
let double = (amountWithPrefix as NSString).doubleValue
number = NSNumber(value: (double))
// number = NSNumber(value: (double / 100))
guard number != 0 as NSNumber else {
return ""
}
return formatter.string(from: number)!
}
}
You don't need to replace any any characters using regex. Just use NSNumberFormatter
extension String {
// formatting text for currency textField
func currencyFormatting() -> String {
if let value = Double(self) {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
if let str = formatter.string(for: value) {
return str
}
}
return ""
}
}
"74154.7".currencyFormatting() // $74,154.70
"74719.4048014544".currencyFormatting() // $74,719.40
You can use following function for valid currency format -
extension Int {
func createCurrencyString() -> String {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.maximumFractionDigits = 0
return formatter.string(from: NSNumber(value: self))!
}
}

Swift String: append 0s after decimal separator

I need to append 0, 1, or 2 0s to a string, depends on its decimal separator, so that
"100", "100." and "100.0" becomes "100.00"
"100.8" becomes "100.80"
"100.85" remains unchanged
I could find the decimal separator and check its distance to end endIndex of the string, but is there an easier way of doing it?
NumberFormatter does this, but the actual string I have, isn't a plain number that can go through a formatter.
For example:
let amount = "123,456,789"
then formatted amount should be "123,456,789.00"
assumption:
the given string has at most one decimal separator with at most two decimal places
So there can't be string like: "123.4.4.5"
Also I want to use the decimal separator from NumberFormatter().decimalSeparator
You could pass the string through a decimal formatter to get the underlying number, and then back again through the formatter to get a formatted string:
let amount = "123,456,789"
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
let number = formatter.number(from: amount)
let newAmountString = formatter.string(from: number!) //"123,456,789.00"
(You should check that number is not nil before force unwrapping it, with if letor guard)
You could wrap this in a function:
func zeroPadding(toString: String) -> String? {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
guard let number = formatter.number(from: toString) else {
return nil
}
return formatter.string(from: number)
}
Here are some test cases:
zeroPadding(toString: "123,456,789") //"123,456,789.00"
zeroPadding(toString: "123,456,789.0") //"123,456,789.00"
zeroPadding(toString: "123,456,789.10") //"123,456,789.10"
zeroPadding(toString: "123,456,789.123") //"123,456,789.12"
zeroPadding(toString: "123.4567") //"123.46"
zeroPadding(toString: "Price: 1€ for a 💩") //nil
Or define it as an extension on String:
extension String {
func withZeroPadding() -> String? {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.maximumFractionDigits = 2
formatter.minimumFractionDigits = 2
guard let number = formatter.number(from: self) else {
return nil
}
return formatter.string(from: number)
}
}
And use it like this:
"123.4.4.5".withZeroPadding() //nil
"12.".withZeroPadding() //"12.00"
"123,456,789".withZeroPadding() //"123,456,789.00"
This is the following code snippet I have tested on Playground, it can be achieved more smartly but for now it is working.
//let amount = "123,456,789.545222323"
//let amount = "123,456,789."
let amount = "123,456,789"
let removeSpaces = amount.replacingOccurrences(of: " ", with: "")
if removeSpaces.count > 0
{
let arrSTR = removeSpaces.components(separatedBy: ".")
if arrSTR.count > 1
{
var strAfterDecimal = arrSTR[1]
if strAfterDecimal.count >= 2
{
strAfterDecimal = strAfterDecimal[0..<2]
}else if strAfterDecimal.count != 0
{
strAfterDecimal = "\(strAfterDecimal)0"
}else
{
strAfterDecimal = "00"
}
let finalSTR = String("\(arrSTR[0]).\(strAfterDecimal)")
print("Final with Decimal - \(finalSTR)")
}else
{
let finalSTR = String(arrSTR[0] + ".00")
print("Final without Decimal - \(finalSTR)")
}
}
extension String {
subscript(_ range: CountableRange<Int>) -> String {
let idx1 = index(startIndex, offsetBy: max(0, range.lowerBound))
let idx2 = index(startIndex, offsetBy: min(self.count, range.upperBound))
return String(self[idx1..<idx2])
}
}

Add remove currency formatting in Swift

I use
func formatAmount(number:NSNumber) -> String {
let formatter = NSNumberFormatter()
formatter.numberStyle = .CurrencyStyle
return formatter.stringFromNumber(number)!
}
for changing the number to currency formatted string, but I need to remove the formatting and get just number, I need to remove the comma and currency symbol. is there any specific way? Please let me know.
I tried
func removeFormatAmount(string:String) -> NSNumber {
let formatter = NSNumberFormatter()
formatter.numberStyle = .NoStyle
formatter.currencySymbol = .None
formatter.currencyGroupingSeparator = .None
return formatter.numberFromString(string)!
}
and this gives me nil value.
UPDATE
I figured out that if text does not contain $ sign then formatting with currency will give nil value so what I did is
if string.containsString("$") {
formatter.numberStyle = .CurrencyStyle
}
return formatter.numberFromString(string)?.floatValue
now it just gives me good result.
You can create a string extension to do that
For Swift 3:
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
}
}
To use:
let currencyString = "$1,000.00"
let amount = currencyString.removeFormatAmount() // 1000.0
func formatAmount(number:NSNumber) -> String{
let formatter = NSNumberFormatter()
formatter.numberStyle = .CurrencyStyle
formatter.currencySymbol = "" // <--------
formatter.currencyGroupingSeparator = "" // <--------
return formatter.stringFromNumber(number)!
}
Swift 4+
This removes currency format from all languages
extension String {
public func removeFormatAmount() -> Double {
let formatter = NumberFormatter()
formatter.locale = Locale.current
formatter.numberStyle = .currency
formatter.currencySymbol = Locale.current.currencySymbol
formatter.decimalSeparator = Locale.current.groupingSeparator
return formatter.number(from: self)?.doubleValue ?? 0.00
}
}
Example:
let currencyString = "$100.00"
let amount = currencyString.removeFormatAmount() // 100.0
NSNumberFormatter has a function numberFromString that you can use
let formatter = NSNumberFormatter()
formatter.numberStyle = .CurrencyStyle
if let number = formatter.numberFromString(string) {
//Use number
}
If you want the string from number it's easy to just build a string from that number afterwards
var number = formatter.numberFromString(string)!
var string = "\(number)"
func removeFormatAmount(string:String) -> NSNumber{
let formatter = NSNumberFormatter()
// specify a locale where the decimalSeparator is a comma
formatter.locale = NSLocale(localeIdentifier: "pt_BR")
formatter.numberStyle = .CurrencyStyle
formatter.currencySymbol = "$"
formatter.decimalSeparator = ","
return formatter.numberFromString(string) ?? 0
}
removeFormatAmount("$15,99") // 15.99

Formatting input for currency with NSNumberFormatter in Swift

I am creating a budget app that allows the user to input their budget as well as transactions. I need to allow the user to enter both pence and pounds from separate text fields and they need to be formatted together with currency symbols. I have this working fine at the moment but would like to make it localised as currently it only works with GBP. I have been struggling to convert NSNumberFormatter examples from Objective-C to Swift.
My first issue is the fact that I need to set the placeholders for the input fields to be specific to the users location. Eg. Pounds and Pence, Dollars and Cents etc...
The second issue is that the values inputted in each of the text fields such as 10216 and 32 need to be formatted and the currency symbol specific to the users location needs to be added. So it would become £10,216.32 or $10,216.32 etc...
Also, I need to use the result of the formatted number in a calculation. So how can I do this without running into issues without running into issues with the currency symbol?
Here's an example on how to use it on Swift 3.
( Edit: Works in Swift 5 too )
let price = 123.436 as NSNumber
let formatter = NumberFormatter()
formatter.numberStyle = .currency
// formatter.locale = NSLocale.currentLocale() // This is the default
// In Swift 4, this ^ was renamed to simply NSLocale.current
formatter.string(from: price) // "$123.44"
formatter.locale = Locale(identifier: "es_CL")
formatter.string(from: price) // $123"
formatter.locale = Locale(identifier: "es_ES")
formatter.string(from: price) // "123,44 €"
Here's the old example on how to use it on Swift 2.
let price = 123.436
let formatter = NSNumberFormatter()
formatter.numberStyle = .CurrencyStyle
// formatter.locale = NSLocale.currentLocale() // This is the default
formatter.stringFromNumber(price) // "$123.44"
formatter.locale = NSLocale(localeIdentifier: "es_CL")
formatter.stringFromNumber(price) // $123"
formatter.locale = NSLocale(localeIdentifier: "es_ES")
formatter.stringFromNumber(price) // "123,44 €"
Swift 3:
If you are looking for a solution that gives you:
"5" = "$5"
"5.0" = "$5"
"5.00" = "$5"
"5.5" = "$5.50"
"5.50" = "$5.50"
"5.55" = "$5.55"
"5.234234" = "5.23"
Please use the following:
func cleanDollars(_ value: String?) -> String {
guard value != nil else { return "$0.00" }
let doubleValue = Double(value!) ?? 0.0
let formatter = NumberFormatter()
formatter.currencyCode = "USD"
formatter.currencySymbol = "$"
formatter.minimumFractionDigits = (value!.contains(".00")) ? 0 : 2
formatter.maximumFractionDigits = 2
formatter.numberStyle = .currencyAccounting
return formatter.string(from: NSNumber(value: doubleValue)) ?? "$\(doubleValue)"
}
I have implemented the solution provided by #NiñoScript as an extension as well:
Extension
// Create a string with currency formatting based on the device locale
//
extension Float {
var asLocaleCurrency:String {
var formatter = NSNumberFormatter()
formatter.numberStyle = .CurrencyStyle
formatter.locale = NSLocale.currentLocale()
return formatter.stringFromNumber(self)!
}
}
Usage:
let amount = 100.07
let amountString = amount.asLocaleCurrency
print(amount.asLocaleCurrency())
// prints: "$100.07"
Swift 3
extension Float {
var asLocaleCurrency:String {
var formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.locale = Locale.current
return formatter.string(from: self)!
}
}
Xcode 11 • Swift 5.1
extension Locale {
static let br = Locale(identifier: "pt_BR")
static let us = Locale(identifier: "en_US")
static let uk = Locale(identifier: "en_GB") // ISO Locale
}
extension NumberFormatter {
convenience init(style: Style, locale: Locale = .current) {
self.init()
self.locale = locale
numberStyle = style
}
}
extension Formatter {
static let currency = NumberFormatter(style: .currency)
static let currencyUS = NumberFormatter(style: .currency, locale: .us)
static let currencyBR = NumberFormatter(style: .currency, locale: .br)
}
extension Numeric {
var currency: String { Formatter.currency.string(for: self) ?? "" }
var currencyUS: String { Formatter.currencyUS.string(for: self) ?? "" }
var currencyBR: String { Formatter.currencyBR.string(for: self) ?? "" }
}
let price = 1.99
print(Formatter.currency.locale) // "en_US (current)\n"
print(price.currency) // "$1.99\n"
Formatter.currency.locale = .br
print(price.currency) // "R$1,99\n"
Formatter.currency.locale = .uk
print(price.currency) // "£1.99\n"
print(price.currencyBR) // "R$1,99\n"
print(price.currencyUS) // "$1.99\n"
Details
Xcode 10.2.1 (10E1001), Swift 5
Solution
import Foundation
class CurrencyFormatter {
static var outputFormatter = CurrencyFormatter.create()
class func create(locale: Locale = Locale.current,
groupingSeparator: String? = nil,
decimalSeparator: String? = nil,
style: NumberFormatter.Style = NumberFormatter.Style.currency) -> NumberFormatter {
let outputFormatter = NumberFormatter()
outputFormatter.locale = locale
outputFormatter.decimalSeparator = decimalSeparator ?? locale.decimalSeparator
outputFormatter.groupingSeparator = groupingSeparator ?? locale.groupingSeparator
outputFormatter.numberStyle = style
return outputFormatter
}
}
extension Numeric {
func toCurrency(formatter: NumberFormatter = CurrencyFormatter.outputFormatter) -> String? {
guard let num = self as? NSNumber else { return nil }
var formatedSting = formatter.string(from: num)
guard let locale = formatter.locale else { return formatedSting }
if let separator = formatter.groupingSeparator, let localeValue = locale.groupingSeparator {
formatedSting = formatedSting?.replacingOccurrences(of: localeValue, with: separator)
}
if let separator = formatter.decimalSeparator, let localeValue = locale.decimalSeparator {
formatedSting = formatedSting?.replacingOccurrences(of: localeValue, with: separator)
}
return formatedSting
}
}
Usage
let price = 12423.42
print(price.toCurrency() ?? "")
CurrencyFormatter.outputFormatter = CurrencyFormatter.create(style: .currencyISOCode)
print(price.toCurrency() ?? "nil")
CurrencyFormatter.outputFormatter = CurrencyFormatter.create(locale: Locale(identifier: "es_ES"))
print(price.toCurrency() ?? "nil")
CurrencyFormatter.outputFormatter = CurrencyFormatter.create(locale: Locale(identifier: "de_DE"), groupingSeparator: " ", style: .currencyISOCode)
print(price.toCurrency() ?? "nil")
CurrencyFormatter.outputFormatter = CurrencyFormatter.create(groupingSeparator: "_", decimalSeparator: ".", style: .currencyPlural)
print(price.toCurrency() ?? "nil")
let formatter = CurrencyFormatter.create(locale: Locale(identifier: "de_DE"), groupingSeparator: " ", decimalSeparator: ",", style: .currencyPlural)
print(price.toCurrency(formatter: formatter) ?? "nil")
Results
$12,423.42
USD12,423.42
12.423,42 €
12 423,42 EUR
12_423.42 US dollars
12 423,42 Euro
Updated for Swift 4 from #Michael Voccola's answer:
extension Double {
var asLocaleCurrency: String {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.locale = Locale.current
let formattedString = formatter.string(from: self as NSNumber)
return formattedString ?? ""
}
}
Note: no force-unwraps, force-unwraps are evil.
Swift 4 TextField Implemented
var value = 0
currencyTextField.delegate = self
func numberFormatting(money: Int) -> String {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.locale = .current
return formatter.string(from: money as NSNumber)!
}
currencyTextField.text = formatter.string(from: 50 as NSNumber)!
func textFieldDidEndEditing(_ textField: UITextField) {
value = textField.text
textField.text = numberFormatting(money: Int(textField.text!) ?? 0 as! Int)
}
func textFieldDidBeginEditing(_ textField: UITextField) {
textField.text = value
}
extension Float {
var convertAsLocaleCurrency :String {
var formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.locale = Locale.current
return formatter.string(from: self as NSNumber)!
}
}
This working for swift 3.1 xcode 8.2.1
Swift 4
formatter.locale = Locale.current
if you want to change locale you can do it like this
formatter.locale = Locale.init(identifier: "id-ID")
// This is locale for Indonesia locale. if you want use as per mobile phone area use it as per upper mention Locale.current
//MARK:- Complete code
let formatter = NumberFormatter()
formatter.numberStyle = .currency
if let formattedTipAmount = formatter.string(from: Int(newString)! as
NSNumber) {
yourtextfield.text = formattedTipAmount
}
add this function
func addSeparateMarkForNumber(int: Int) -> String {
var string = ""
let formatter = NumberFormatter()
formatter.locale = Locale.current
formatter.numberStyle = .decimal
if let formattedTipAmount = formatter.string(from: int as NSNumber) {
string = formattedTipAmount
}
return string
}
using:
let giaTri = value as! Int
myGuessTotalCorrect = addSeparateMarkForNumber(int: giaTri)