Lutando com NSNumberFormatter em Swift para moeda

86

Estou criando um aplicativo de orçamento que permite ao usuário inserir seu orçamento, bem como transações. Preciso permitir que o usuário insira pence e libras em campos de texto separados e eles precisam ser formatados junto com os símbolos de moeda. Eu estou funcionando bem no momento, mas gostaria de torná-lo localizado, pois atualmente ele só funciona com GBP. Tenho lutado para converter exemplos NSNumberFormatter do Objective C para o Swift.

Meu primeiro problema é o fato de que preciso definir os espaços reservados para os campos de entrada para serem específicos ao local dos usuários. Por exemplo. Libras e Pence, Dólares e Cêntimos etc ...

O segundo problema é que os valores inseridos em cada um dos campos de texto, como 10216 e 32, precisam ser formatados e o símbolo de moeda específico para a localização do usuário precisa ser adicionado. Portanto, seria £ 10.216,32 ou $ 10.216,32 etc ...

Além disso, preciso usar o resultado do número formatado em um cálculo. Então, como posso fazer isso sem ter problemas sem ter problemas com o símbolo da moeda?

Qualquer ajuda seria muito apreciada.

user3746428
fonte
2
você pode postar um exemplo de código que não funciona?
NiñoScript

Respostas:

205

Aqui está um exemplo de como usá-lo no Swift 3. ( Editar : Funciona no Swift 4 também)

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

Aqui está o exemplo antigo de como usá-lo no 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
fonte
Obrigado. Eu editei minha pergunta e fui mais específico.
user3746428
Com base no exemplo que você forneceu, consegui implementar a formatação de número em meu programa, para que esse bit seja classificado. Agora só preciso descobrir como definir os espaços reservados para os campos de texto com base na localização dos usuários.
user3746428
2
Não há necessidade de convertê-lo para NSNumber, você pode usar o método do formatador func string (para obj: Any?) -> String ?. Então você só precisa usar em string(for: price)vez destring(from: price)
Leo Dabus
1
@LeoDabus você está certo, eu não sabia sobre esse método, mas não tenho certeza se devo editar minha resposta, pois acho que prefiro usar a API do NumberFormatter e ser explícito sobre o uso do NSNumber em vez de deixá-lo implicitamente lance-o para dentro.
NiñoScript
Observe que o resultado de formatter.string (de :) é uma String opcional, não uma String (conforme implícito nos comentários), portanto, precisará ser desembrulhado antes do uso.
Ali Beadle de
25

Swift 3:

Se você está procurando uma solução que ofereça:

  • "5" = "$ 5"
  • "5.0" = "$ 5"
  • "5,00" = "$ 5"
  • "5,5" = "$ 5,50"
  • "5,50" = "$ 5,50"
  • "5,55" = "$ 5,55"
  • "5,234234" = "5,23"

Use o seguinte:

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
fonte
Não há necessidade de inicializar um novo objeto NSNumber, você pode usar o método do formatador em func string(for obj: Any?) -> String?vez destring(from:)
Leo Dabus
19

Eu implementei a solução fornecida por @NiñoScript como uma extensão também:

Extensão

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

Uso:

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)!
    }
}
Michael Voccola
fonte
extensão deve estender FloatingPoint para Swift 3 versão e string (from: o método é para NSNumber. Para os tipos FlotingPoint, você precisa usar o método string (para :). Publiquei uma extensão Swift 3
Leo Dabus
Não use tipos flutuantes para moeda, use decimal.
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
fonte
3
Não use tipos flutuantes para moeda, use decimal.
adnako
7

Detalhes

  • Xcode 10.2.1 (10E1001), Swift 5

Solução

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

Uso

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

Resultados

$12,423.42
USD12,423.42
12.423,42 €
12 423,42 EUR
12_423.42 US dollars
12 423,42 Euro
Vasily Bodnarchuk
fonte
3

Atualizado para Swift 4 a partir da resposta de @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 ?? ""
    }
}

Nota: nenhum desempacotamento forçado, o desempacotamento forçado é mau.

Kakubei
fonte
2

Swift 4 TextField implementado

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

Isso funciona para swift 3.1 xcode 8.2.1

du Phung
fonte
Embora este trecho de código seja bem-vindo e possa fornecer alguma ajuda, seria muito melhor se incluísse uma explicação de como e por que isso resolve o problema. Lembre-se de que você está respondendo às perguntas dos leitores no futuro, não apenas da pessoa que está perguntando agora! Por favor edite sua resposta para adicionar explicação, e dar uma indicação do que limitações e premissas se aplicam.
Toby Speight
Não use tipos flutuantes para moeda, use decimal.
adnako
0

Swift 4

formatter.locale = Locale.current

se você quiser mudar de local, você pode fazer assim

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

// Este é o local para o local da Indonésia. se você quiser usar de acordo com a área do telefone móvel, use-o de acordo com a menção superior 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
fonte
0

adicione esta função

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
}

usando:

let giaTri = value as! Int
myGuessTotalCorrect = addSeparateMarkForNumber(int: giaTri)
codificadores
fonte