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.


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

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

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

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"

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

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.


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
}

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


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
}

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)
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.