如何使用 Swift 在文本字段(从右到左)上输入货币格式?

Posted

技术标签:

【中文标题】如何使用 Swift 在文本字段(从右到左)上输入货币格式?【英文标题】:How to input currency format on a text field (from right to left) using 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 上了​​吗!? 【参考方案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).mapString($0).filter $0.toInt() != nil .mapCharacter($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 在这里发布问题:***.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!).mapString($0).filter $0.toInt() != nil .mapCharacter($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!)!)"

【讨论】:

你有没有机会把它转换成 SwiftUI? @Learn2Code ***.com/a/65783711/2303865【参考方案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 ""

【讨论】:

以上是关于如何使用 Swift 在文本字段(从右到左)上输入货币格式?的主要内容,如果未能解决你的问题,请参考以下文章

在团队资源管理器中显示从右到左的文本

Swift - 从右到左执行 eguewithidentifier 转换样式

从右到左 SnackBar

React Native:如何让 <TextInput/> 从右到左开始?

Python中从右到左的语言

ios / xcode 使文本书写方向从右到左