如何使用 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
。
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 转换样式