【问题标题】:How to input currency format on a text field (from right to left) using Swift?如何使用 Swift 在文本字段(从右到左)上输入货币格式?
【发布时间】:2015-04-21 20:57:45
【问题描述】:

我有一个号码,比如说0.00

  • 当用户点击 1. 我们应该有0.01
  • 当用户点击 2. 我们应该显示0.12
  • 当用户点击 3. 我们应该显示1.23
  • 当用户点击 4. 我们应该显示12.34

我怎样才能用 Swift 做到这一点?

【问题讨论】:

  • 为了 SO 帖子的可读性,请不要在您的问题中包含“bolo”、“提前感谢”或其他对您的问题没有帮助的文字。
  • @milo526 对不起!我会解决的
  • 你的最后一个数字应该是 12.34,不是吗?
  • @ThomasKilian 是的,你是对的
  • 有人把它移植到 SwiftUI 上了​​吗!?

标签: ios swift string uitextfield nsnumberformatter


【解决方案1】:

适用于 Swift 3。在文本字段中输入货币格式(从右到左)

override func viewDidLoad() {
    super.viewDidLoad()

    textField.addTarget(self, action: #selector(myTextFieldDidChange), for: .editingChanged)
}

@objc func myTextFieldDidChange(_ textField: UITextField) {

    if let amountString = textField.text?.currencyInputFormatting() {
        textField.text = amountString
    }
}

extension String {

    // formatting text for currency textField
    func currencyInputFormatting() -> String {
    
        var number: NSNumber!
        let formatter = NumberFormatter()
        formatter.numberStyle = .currencyAccounting
        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 ""
        }
    
        return formatter.string(from: number)!
    }
}

【讨论】:

  • 这个很酷并且工作正常,但是有没有办法使用键盘上的删除按钮删除最后输入的数字?目前按下按钮时什么也没有发生。感谢您的帮助!
  • 它很奇怪,但你的例子不起作用,我尝试了另一种解决方案,但删除按钮什么也没做,所以我想我必须进一步调查......
  • 如果您将直接值传递给 UITextField 而不是键盘类型,这将不起作用。 !!
  • 这个答案有一些问题。此答案假定具有固定货币符号的固定货币格式。为什么?只有一些国家使用$ 符号。并非所有国家/地区的货币都使用两位小数。
  • 当货币显示在金额的右侧时,实际上这是行不通的(这取决于语言环境电话设置)-然后您无法删除该值
【解决方案2】:

您可以创建一个货币文本字段子类 UITextField。为 UIControlEvents .editingChanged 添加一个目标。添加选择器方法以过滤文本字段字符串中的数字。从字符串中过滤出所有非数字后,您可以使用 NumberFormatter 再次格式化您的号码,如下所示:

Xcode 11.5 • Swift 5.2 或更高版本

import UIKit

class CurrencyField: UITextField {
    var decimal: Decimal { string.decimal / pow(10, Formatter.currency.maximumFractionDigits) }
    var maximum: Decimal = 999_999_999.99
    private var lastValue: String?
    var locale: Locale = .current {
        didSet {
            Formatter.currency.locale = locale
            sendActions(for: .editingChanged)
        }
    }
    override func willMove(toSuperview newSuperview: UIView?) {
        // you can make it a fixed locale currency if needed
        // self.locale = Locale(identifier: "pt_BR") // or "en_US", "fr_FR", etc
        Formatter.currency.locale = locale
        addTarget(self, action: #selector(editingChanged), for: .editingChanged)
        keyboardType = .numberPad
        textAlignment = .right
        sendActions(for: .editingChanged)
    }
    override func deleteBackward() {
        text = string.digits.dropLast().string
        // manually send the editingChanged event
        sendActions(for: .editingChanged)
    }
    @objc func editingChanged() {
        guard decimal <= maximum else {
            text = lastValue
            return
        }
        text = decimal.currency
        lastValue = text
    }
}

extension CurrencyField {
    var doubleValue: Double { (decimal as NSDecimalNumber).doubleValue }
}

extension UITextField {
     var string: String { text ?? "" }
}

extension NumberFormatter {
    convenience init(numberStyle: Style) {
        self.init()
        self.numberStyle = numberStyle
    }
}

private extension Formatter {
    static let currency: NumberFormatter = .init(numberStyle: .currency)
}

extension StringProtocol where Self: RangeReplaceableCollection {
    var digits: Self { filter (\.isWholeNumber) }
}

extension String {
    var decimal: Decimal { Decimal(string: digits) ?? 0 }
}

extension Decimal {
    var currency: String { Formatter.currency.string(for: self) ?? "" }
}

extension LosslessStringConvertible {
    var string: String { .init(self) }
}

视图控制器

class ViewController: UIViewController {

    @IBOutlet weak var currencyField: CurrencyField!
    override func viewDidLoad() {
        super.viewDidLoad()
        currencyField.addTarget(self, action: #selector(currencyFieldChanged), for: .editingChanged)
        currencyField.locale = Locale(identifier: "pt_BR") // or "en_US", "fr_FR", etc
    }
    @objc func currencyFieldChanged() {
        print("currencyField:",currencyField.text!)
        print("decimal:", currencyField.decimal)
        print("doubleValue:",(currencyField.decimal as NSDecimalNumber).doubleValue, terminator: "\n\n")
    }
}

Sample project


本文的SwiftUI版本here

【讨论】:

  • 你写了这行:let cleanText = String(Array(sender.text).map{String($0)}.filter{ $0.toInt() != nil }.map{Character($0)} ) as NSString。为什么要做出如此复杂的陈述?用临时变量将其分成 3 或 4 块。它使阅读、调试和维护变得更加容易,并且编译器优化了发布版本中的临时变量。
  • 这非常简单!但是我在一个金融应用程序上工作并让用户更改他的货币,所以我需要在文本字段上进行设置。我尝试使用Formatter.currency.locale = Locale(identifier: "es_CL") text = Formatter.currency.string(from:(Double(string.numbers.integer) / 100) as NSNumber) 设置语言环境,但是例如使用此语言环境,文本字段不再起作用?你知道为什么吗?
  • @LeoDabus 在这里发布问题:stackoverflow.com/questions/41711544/… 我想问题不在于如何设置语言环境,而在于语言环境本身,因为 NumberFormatter 不会为这个语言环境输出小数
  • @LeoDabus : 请提供任何 Objective-c 解决方案
【解决方案3】:

我从 Leo Dabus 的回答开始(这对我来说不是开箱即用的),并且在尝试简化并使其正常工作的过程中最终得到了这个,如果我认为这是非常精简和干净的自己说吧?

class CurrencyTextField: UITextField {

    /// The numbers that have been entered in the text field
    private var enteredNumbers = ""

    private var didBackspace = false

    var locale: Locale = .current

    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }

    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }

    private func commonInit() {
        addTarget(self, action: #selector(editingChanged), for: .editingChanged)
    }

    override func deleteBackward() {
        enteredNumbers = String(enteredNumbers.dropLast())
        text = enteredNumbers.asCurrency(locale: locale)
        // Call super so that the .editingChanged event gets fired, but we need to handle it differently, so we set the `didBackspace` flag first
        didBackspace = true
        super.deleteBackward()
    }

    @objc func editingChanged() {
        defer {
            didBackspace = false
            text = enteredNumbers.asCurrency(locale: locale)
        }

        guard didBackspace == false else { return }

        if let lastEnteredCharacter = text?.last, lastEnteredCharacter.isNumber {
            enteredNumbers.append(lastEnteredCharacter)
        }
    }
}

private extension Formatter {
    static let currency: NumberFormatter = {
        let formatter = NumberFormatter()
        formatter.numberStyle = .currency
        return formatter
    }()
}

private extension String {
    func asCurrency(locale: Locale) -> String? {
        Formatter.currency.locale = locale
        if self.isEmpty {
            return Formatter.currency.string(from: NSNumber(value: 0))
        } else {
            return Formatter.currency.string(from: NSNumber(value: (Double(self) ?? 0) / 100))
        }
    }
}

【讨论】:

  • 如何在 uitextfield 中使用它??
  • @danu 您将视图控制器中的文本字段设置为 CurrencyTextField 而不是 UITextField
  • 并非所有货币都有 2 个小数位。请注意,isNumber 也允许使用像“⅚”这样的小数字符。
  • 为什么不简单地enteredNumbers.popLast()
【解决方案4】:

试试这段代码:

struct DotNum {
  private var fraction:String = ""
  private var intval:String = ""
  init() {}
  mutating func enter(s:String) {
    if count(fraction) < 2 {
      fraction = s + fraction
    } else {
      intval = s + intval
    }
  }
  private var sFract:String {
    if count(fraction) == 0 { return "00" }
    if count(fraction) == 1 { return "0\(fraction)" }
    return fraction
  }
  var stringVal:String {
    if intval == ""  { return "0.\(sFract)" }
    return "\(intval).\(sFract)"
  }
}
var val = DotNum()
val.enter("1")
val.stringVal
val.enter("2")
val.stringVal
val.enter("3")
val.stringVal
val.enter("4")
val.stringVal

【讨论】:

  • 除了纯 Swift 之外,不依赖任何框架的最佳答案。
【解决方案5】:

我的最终代码感谢您的帮助

extension Double {
            var twoDigits: Double {
                let nf = NSNumberFormatter()
                nf.numberStyle = NSNumberFormatterStyle.DecimalStyle
                nf.minimumFractionDigits = 2
                nf.maximumFractionDigits = 2
                return self
            }
    }
    var cleanText:String!
            let number:String = sender.currentTitle as String!
            if(amountDisplay.text != nil)
            {
                cleanText = String(Array(amountDisplay.text!).map{String($0)}.filter{ $0.toInt() != nil }.map{Character($0)} ) as String
                cleanText = cleanText + number
            }else{
                cleanText = number
            }

            amount = (Double(cleanText.toInt()!) / 100).twoDigits
            formatter.locale = NSLocale(localeIdentifier: currencies[current_currency_index])
            amountDisplay.text = "\(formatter.stringFromNumber(amount!)!)"

【讨论】:

【解决方案6】:

这是 swift 2 的代码

@IBOutlet weak var txtAmount: UITextField!

//MARK: - UITextField Delegate -
    func textField(textField: UITextField, shouldChangeCharactersInRange range: NSRange, replacementString string: String) -> Bool{

        if string.characters.count == 0 {
            return true
        }

        let userEnteredString = textField.text ?? ""
        var newString = (userEnteredString as NSString).stringByReplacingCharactersInRange(range, withString: string) as NSString
        newString = newString.stringByReplacingOccurrencesOfString(".", withString: "")

        let centAmount : NSInteger = newString.integerValue
        let amount = (Double(centAmount) / 100.0)

        if newString.length < 16 {
            let str = String(format: "%0.2f", arguments: [amount])
            txtAmount.text = str
        }

        return false //return false for exact out put
    }

注意:从情节提要或以编程方式连接 textField 的委托

【讨论】:

    【解决方案7】:

    只是为了好玩:将Thomas's answer(完整的学分 - 并指出 - 给他)复制到一个文件中,以作为 Swift 4.1 脚本运行(有一些小的修复):

    dotnum.swift:

    #!/usr/bin/swift
    
    struct DotNum {
        private var fraction:String = ""
        private var intval:String = ""
        init() {}
        mutating func enter(_ s:String) {
            if fraction.count < 2 {
              fraction = s + fraction
            } else {
              intval = s + intval
            }
        }
        private var sFract:String {
            if fraction.count == 0 { return "00" }
            if fraction.count == 1 { return "0\(fraction)" }
            return fraction
        }
        var stringVal:String {
            if intval == ""  { return "0.\(sFract)" }
            return "\(intval).\(sFract)"
        }
    }
    
    var val = DotNum()
    val.enter("1")
    print(val.stringVal)
    val.enter("2")
    print(val.stringVal)
    val.enter("3")
    print(val.stringVal)
    val.enter("4")
    print(val.stringVal)
    

    然后在终端中运行它:

    $ chmod +x dotnum.swift
    $ ./dotnum.swift
    0.01
    0.21
    3.21
    43.21
    

    【讨论】:

      【解决方案8】:

      感谢这里的每一个人。从这里的所有答案中,我设法得出了我的答案。

      首先我将 textField 的初始值设置为:

      private func commonInit() { 
          amountTextField.text = "0.00"
      }
      

      然后我使用 UITextFieldDelegate 来获取输入值和当前的 textview.text:

      func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
          //Need to check if the textfield.text can be evaluated as number or not before passing it to the function
          //Get the current text value, and current user input and pass it to the 
          let formattedAmount = formatAmount(oldAmount: textField.text, userInput: string)
          textField.text = formattedAmount
          return false
      }
      

      这里是我的私有函数来格式化数字从右到左移动:

      private func formatAmount(currentText: String, userInput: String) -> String {
          let amount = currentText.components(separatedBy: ".")
          var intValue: String = amount[0]
          var decimalValue: String = amount[1]
          
      
          //backspace registered, need to move the number to the right
          if userInput.isEmpty {
              decimalValue.remove(at: decimalValue.index(before: decimalValue.endIndex))
              decimalValue = intValue.last!.string + decimalValue
              intValue.remove(at: intValue.index(before: intValue.endIndex))
              if intValue.isEmpty {
                  intValue = "0"
              }
          } else {
              
              //Need to consider if user paste value
              if userInput.count > 2 {
                  decimalValue = String(userInput.suffix(2))
                  intValue = String(userInput.dropLast(2))
              } else {
                  decimalValue = rmAmount[1] + userInput
                  
                  //Add to int value (move to the right)
                  intValue = intValue + decimalValue.first!.string
                  
                  if Int(intValue) == 0 {
                      intValue = "0"      //00 -> 0
                  } else if intValue.first == "0" {
                      //remove 0 from at the first position in intValue
                      intValue.remove(at: intValue.startIndex)    //01 -> 1
                  }
                  
                  //Remove tenth place from decimal value since it goes to Int already
                  decimalValue.remove(at: decimalValue.startIndex)
              }
          }
          return intValue + "." + decimalValue
      }
      

      基本上就是这样。您可以自己主动添加其他额外的实现。如果我的实现有任何问题,请告诉我。

      PS:这当然只适用于某些货币,就我而言,我的应用程序仅针对该本地设置,所以这就是我使用这种方式的原因。

      【讨论】:

        【解决方案9】:

        在对建议的答案进行大量试验和错误之后,我找到了一个非常简单的解决方案:

        需要在视图的设置中调用 textField 的设置。

        在 switch 语句中,如果用户输入一个介于 0 和 9 之间的数字,则该数字将添加到之前的字符串值中。默认情况下覆盖退格按钮并从字符串中删除最后一个字符。

        numberFormatter 的语言环境设置为当前,因此它适用于不同的货币。

        func setupTextField() {
                textField.delegate = self
                textField.tintColor = .clear
                textField.keyboardType = .numberPad
        }
        
        
        func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
            setFormattedAmount(string)
            
            return false
        }
        
        private func setFormattedAmount(_ string: String) {
            switch string {
            case "0", "1", "2", "3", "4", "5", "6", "7", "8", "9":
                amountString = amountString + string
            default:
                if amountString.count > 0 {
                    amountString.removeLast()
                }
            }
            
            let amount = (NSString(string: amountString).doubleValue) / 100
            textField.text = formatAmount(amount)
        }
        
        private func formatAmount(_ amount: Double) -> String {
            let formatter = NumberFormatter()
            formatter.numberStyle = .currency
            formatter.locale = .current
            
            if let amount = formatter.string(from: NSNumber(value: amount)) {
                return amount
            }
            
            return ""
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-08-02
          • 1970-01-01
          • 2013-06-18
          • 1970-01-01
          相关资源
          最近更新 更多