in-app-purchase price locale number formatter (what is behaviour10_4) - swift

Working on price localisation for IAP and while the below codes both work the same (simulator) I would like to know what is this behavior10_4?
Apple's doc doesn't say much and googling and SO also don't have much info about it.
https://developer.apple.com/documentation/foundation/dateformatter/behavior/behavior10_4
extension IAPHelper {
// https://stackoverflow.com/a/42009726/14414215
static func priceFor(_ product: SKProduct) -> String? {
let formatter = NumberFormatter()
formatter.formatterBehavior = .behavior10_4
formatter.numberStyle = .currency
formatter.locale = product.priceLocale
return formatter.string(from: product.price)
}
// Adapted from https://developer.apple.com/documentation/storekit/skproduct/1506094-price
static func localizedPrice(_ product: SKProduct) -> String? {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.locale = product.priceLocale
return formatter.string(from: product.price)
}
}
print("Currency1:\(IAPHelper.priceFor(p) ?? "")") // USD 0.99
print("Currency2:\(IAPHelper.localizedPrice(p) ?? "")") // USD 0.99

Related

Second decimal place gets truncated if it is 0

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

How to add custom init for String extension?

How can I add custom init method for String extension?
extension String {
init(_ amount: Double, decimalPlaces: UInt) {
self.init()
let decimalFormat = "%0.\(String(decimalPlaces))f"
let currencyAmount = String(format: decimalFormat, amount)
let currencySign = NSLocalizedString("Defaults.CurrencySign", comment: "currency sign")
let formattedString = "\(currencySign)\(currencyAmount)"
// How to set self to `formattedString` ?
}
}
As result I want to see something like this:
let price = Double(155.15)
let formattedPrice = String(price, decimalPlaces: 2) // formattedPrice = "$155.15"
UPDATED: Final solution
extension String {
init?(currencyAmount: Double) {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.locale = Locale(identifier: NSLocalizedString("Defaults.LocaleCurrencyFormat", comment: "currency sign")) // Defaults.LocaleCurrencyFormat equal "es_US" for US
let formattedAmount = formatter.string(from: NSNumber(value: currencyAmount)) ?? ""
self.init(formattedAmount)
}
}
Edit: Don't format currencies yourself.
However you might think currencies are formatted, you're almost certainly wrong. Just compare:
US/Canada: $3,490,000.89
French Canadian: 3 490 000,89 $
France: 3 490 000,89 €
Germany: 3.490.000,89 €
Instead, use NumberFormatter with numberStyle set to .currency, with a specified locale.
let currencyFormatter = NumberFormatter()
currencyFormatter.usesGroupingSeparator = true
currencyFormatter.numberStyle = .currency
currencyFormatter.locale = Locale.current
let priceString = currencyFormatter.string(from: 9999.99)!
print(priceString) // Displays $9,999.99 in the US locale
Original answer:
The initializers (and mutating methods) of value types can simply assign directly to self:
import Foundation
extension String {
init(_ amount: Double, decimalPlaces: UInt) {
let currencyAmount = String(format: "%\(decimalPlaces).f", amount)
let currencySign = NSLocalizedString("Defaults.CurrencySign", comment: "currency sign")
self = "\(currencySign)\(currencyAmount)"
}
}
let price = Double(155.15)
let formattedPrice = String(price, decimalPlaces: 2) // formattedPrice = "$155.15"

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

Swift how to format a large number with thousands separators?

Is there a simple command to format 1.60543e+06 to 1,605,436???
resultFV.text = String.localizedStringWithFormat("%f", fv)
does not get it.
In Swift 3,
NumberFormatter.localizedString(from: NSNumber(value: whatever), number: NumberFormatter.Style.decimal)
Swift Xcode 6.3, SOLVED (I decided to leave the $ in the code). If you don't want a $ in the output, change .CurrencyStyle to .DecimalStyle
var fv = 3534234.55
var formatter = NSNumberFormatter()
formatter.numberStyle = .CurrencyStyle
formatter.maximumFractionDigits = 0;
resultFV.text = formatter.stringFromNumber(fv) // result: $3,534,235 –
You should use a NumberFormatter for that:
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
resultFV.text = numberFormatter.string(from: fv)
Update for Swift 4.1 currency string:
let balance = 1234567
let formatter = NumberFormatter()
formatter.numberStyle = .currency
formatter.maximumFractionDigits = 0
let result = formatter.string(from: NSNumber(value: balance))
// result: $1,234,567
You can change formatter.numberStyle to .decimal to leave it as number without "$" sign.
You can achieve this by using String initializers in Swift 3+:
// 1605436
let value: Int = 1605436
// "1,605,436" where Locale == en_US
let formattedInt = String(format: "%d", locale: Locale.current, value)
// "1,605,436" where Locale == en_US
let formattedDouble = String(format: "%.0f", locale: Locale.current, Double(value))
Update for Swift 5
var unformattedValue : Double = 3534234.55
var formatter = NumberFormatter()
formatter.numberStyle = .currency // or .decimal if desired
formatter.maximumFractionDigits = 2; //change as desired
formatter.locale = Locale.current // or = Locale(identifier: "de_DE"), more locale identifier codes: https://gist.github.com/jacobbubu/1836273
var displayValue : String = formatter.string(from: NSNumber(value: unformattedValue))! // displayValue: "$3,534,235" ```
Why don't you limit the precision, like ".0f"
resultFV.text = String.localizedStringWithFormat("%.0f", fv)
Updated Answer:
var formatter: NSNumberFormatter = NSNumberFormatter()
formatter.numberStyle = NSNumberFormatterStyle.DecimalStyle;
var formattedStr: NSString = formatter.stringFromNumber(NSNumber(double: fv))!
resultFV.text = formattedStr
Updated
Using Data Formatting available in Foundation for macOS 12.0+, iOS 15.0+, tvOS 15.0+, and watchOS 8.0+.
let number: Int = 1000
let formatted = number.formatted(.number.grouping(.never))
print(formatted)
console output: "1000"
number can be any BinaryFloatingPoint or BinaryInteger

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)