Walcząc z NSNumberFormatter w Swift o walutę

86

Tworzę aplikację budżetową, która pozwala użytkownikowi wprowadzić budżet, a także transakcje. Muszę pozwolić użytkownikowi na wprowadzanie zarówno pensów, jak i funtów z oddzielnych pól tekstowych i muszą być one sformatowane razem z symbolami walut. W tej chwili to działa dobrze, ale chciałbym, aby było zlokalizowane, ponieważ obecnie działa tylko z GBP. Próbowałem ukryć przykłady NSNumberFormatter od Objective C do Swift.

Moim pierwszym problemem jest to, że muszę ustawić symbole zastępcze dla pól wejściowych, aby były specyficzne dla lokalizacji użytkowników. Na przykład. Funty i pensy, dolary i centy itp.

Drugą kwestią jest to, że wartości wprowadzane w każdym z pól tekstowych, takich jak 10216 i 32, muszą zostać sformatowane i należy dodać symbol waluty właściwy dla lokalizacji użytkownika. Stałby się więc 10216,32 GBP lub 10216,32 USD itd.

Muszę również użyć wyniku sformatowanej liczby w obliczeniach. Jak więc mogę to zrobić bez napotykania problemów bez napotykania problemów z symbolem waluty?

Każda pomoc byłaby bardzo mile widziana.

user3746428
źródło
2
czy możesz zamieścić przykład niedziałającego kodu?
NiñoScript,

Odpowiedzi:

205

Oto przykład, jak go używać w Swift 3. ( Edycja : działa również w Swift 4)

let price = 123.436 as NSNumber

let formatter = NumberFormatter()
formatter.numberStyle = .currency
// formatter.locale = NSLocale.currentLocale() // This is the default
// In Swift 4, this ^ has been 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 €"

Oto stary przykład, jak go używać w 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 €"
NiñoScript
źródło
Dzięki. Zredagowałem moje pytanie i przedstawiłem je bardziej szczegółowo.
user3746428
Na podstawie podanego przez ciebie przykładu udało mi się zaimplementować formatowanie liczb do mojego programu, więc ten bit jest posortowany. Teraz muszę tylko dowiedzieć się, jak ustawić symbole zastępcze pól tekstowych na podstawie lokalizacji użytkowników.
user3746428
2
Nie ma potrzeby rzutowania go na NSNumber, możesz użyć metody formatującej func string (dla obj: Any?) -> String ?. Więc po prostu musisz użyć string(for: price)zamiaststring(from: price)
Leo Dabus
1
@LeoDabus masz rację, nie wiedziałem o tej metodzie, nie jestem jednak pewien, czy powinienem edytować moją odpowiedź, ponieważ myślę, że wolałbym użyć interfejsu API NumberFormatter i wyraźnie powiedzieć o używaniu NSNumber, niż pozwolić mu niejawnie wrzuć to do środka.
NiñoScript
Zwróć uwagę, że wynik działania formatter.string (from :) jest opcjonalnym ciągiem, a nie ciągiem (jak wynika z komentarzy), więc przed użyciem będzie trzeba go rozpakować.
Ali Beadle,
25

Swift 3:

Jeśli szukasz rozwiązania, które zapewni Ci:

  • „5” = „5 USD”
  • „5,0” = „5 USD”
  • „5,00” = „5 USD”
  • „5,5” = „5,50 USD”
  • „5,50” = „5,50 USD”
  • „5,55” = „5,55 USD”
  • „5,234234” = „5,23”

Użyj następujących:

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)"
}
Gregg
źródło
Nie ma potrzeby, aby zainicjować nowy obiekt NSNumber można użyć formater metody func string(for obj: Any?) -> String?zamiaststring(from:)
Leo Dabus
19

Wdrożyłem rozwiązanie dostarczone przez @ NiñoScript również jako rozszerzenie:

Rozbudowa

// 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)!
    }
}

Stosowanie:

let amount = 100.07
let amountString = amount.asLocaleCurrency
print(amount.asLocaleCurrency())
// prints: "$100.07"

Szybki 3

    extension Float {
    var asLocaleCurrency:String {
        var formatter = NumberFormatter()
        formatter.numberStyle = .currency
        formatter.locale = Locale.current
        return formatter.string(from: self)!
    }
}
Michael Voccola
źródło
rozszerzenie powinno być rozszerzeniem FloatingPoint dla wersji Swift 3 i string (od: metoda jest dla NSNumber. Dla typów FlotingPoint musisz użyć string (dla :) metoda.
Wysłałem
Nie używaj typów zmiennoprzecinkowych dla waluty, używaj dziesiętnych.
adnako
17

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"
Leo Dabus
źródło
3
Nie używaj typów zmiennoprzecinkowych dla waluty, używaj dziesiętnych.
adnako
7

Detale

  • Xcode 10.2.1 (10E1001), Swift 5

Rozwiązanie

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
    }
}

Stosowanie

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

Wyniki

$12,423.42
USD12,423.42
12.423,42 €
12 423,42 EUR
12_423.42 US dollars
12 423,42 Euro
Wasilij Bodnarczuk
źródło
3

Zaktualizowano dla Swift 4 z odpowiedzi @Michael Voccola:

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

Uwaga: brak rozpinania na siłę, rozpinanie na siłę są złe.

kakubei
źródło
2

Wdrożono Swift 4 TextField

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
}
Dary
źródło
0
extension Float {
    var convertAsLocaleCurrency :String {
        var formatter = NumberFormatter()
        formatter.numberStyle = .currency
        formatter.locale = Locale.current
        return formatter.string(from: self as NSNumber)!
    }
}

To działa dla Swift 3.1 xcode 8.2.1

du Phung
źródło
Choć ten fragment kodu jest mile widziany i może zapewnić jakąś pomoc, byłoby znacznie się poprawiła, gdyby obejmowała wyjaśnienie z how i dlaczego to rozwiązuje problem. Pamiętaj, że odpowiadasz na pytanie do czytelników w przyszłości, a nie tylko osoba, która zapyta teraz! Proszę edytować swoje odpowiedzi, aby dodać wyjaśnienie, i dać wskazówkę co zastosować ograniczenia i założenia.
Toby Speight
Nie używaj typów zmiennoprzecinkowych dla waluty, używaj dziesiętnych.
adnako
0

Szybki 4

formatter.locale = Locale.current

jeśli chcesz zmienić lokalizację, możesz to zrobić w ten sposób

formatter.locale = Locale.init(identifier: "id-ID") 

// To jest ustawienie regionalne dla Indonezji. jeśli chcesz korzystać z obszaru telefonu komórkowego, użyj go zgodnie z powyższą wzmianką Locale.current

//MARK:- Complete code
let formatter = NumberFormatter()
formatter.numberStyle = .currency
    if let formattedTipAmount = formatter.string(from: Int(newString)! as 
NSNumber) { 
       yourtextfield.text = formattedTipAmount
}
Shakeel Ahmed
źródło
0

dodaj tę funkcję

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
}

za pomocą:

let giaTri = value as! Int
myGuessTotalCorrect = addSeparateMarkForNumber(int: giaTri)
programistów
źródło