如何让用户在 SwiftUI 文本字段中仅使用数字输入货币,同时保留 $ 和 .?
Posted
技术标签:
【中文标题】如何让用户在 SwiftUI 文本字段中仅使用数字输入货币,同时保留 $ 和 .?【英文标题】:How to make it so that user enters currency only with numbers in SwiftUI textfield, while preserving the $ and the .? 【发布时间】:2021-01-18 22:37:16 【问题描述】:现在,我有以下内容:
private var currencyFormatter: NumberFormatter =
let f = NumberFormatter()
// allow no currency symbol, extra digits, etc
f.isLenient = true
f.numberStyle = .currency
return f
()
TextField("Total", value: $totalInput, formatter: currencyFormatter)
.font(.largeTitle)
.padding()
.background(Color.white)
.foregroundColor(Color.black)
.multilineTextAlignment(.center)
我希望文本字段以 $0.00 作为占位符开始,但是当用户开始输入时,前两个输入将以美分填充...所以 5055 将逐渐显示为:
第 1 步(用户点击 5):0.05 美元 第 2 步(用户点击 0):0.50 美元 第 3 步(用户点击 5):5.05 美元 第 4 步(用户点击 5):50.55 美元
如果金额大于 999 美元,则会插入逗号。
如何做到这一点?现在我的 totalInput 类型是 Double?。
【问题讨论】:
【参考方案1】:要创建一个允许用户从右到左键入金额的货币字段,您需要一个可观察对象(绑定管理器)、一个货币数字格式化程序,并使用 onChange 方法观察每次值发生变化时:
import SwiftUI
struct ContentView: View
@ObservedObject private var currencyManager = CurrencyManager(amount: 0)
@ObservedObject private var currencyManagerUS = CurrencyManager(
amount: 0,
locale: .init(identifier: "en_US")
)
@ObservedObject private var currencyManagerUK = CurrencyManager(
amount: 0,
locale: .init(identifier: "en_UK")
)
@ObservedObject private var currencyManagerFR = CurrencyManager(
amount: 0,
locale: .init(identifier: "fr_FR")
)
@ObservedObject private var currencyManagerBR = CurrencyManager(
amount: 100,
maximum: 100,
locale: .init(identifier: "pt_BR")
)
var body: some View
VStack(alignment: .trailing, spacing: 0)
Spacer()
Group
Text("Locale currency")
TextField(currencyManager.string, text: $currencyManager.string)
.keyboardType(.numberPad)
.multilineTextAlignment(.trailing)
.onChange(of: currencyManager.string, perform: currencyManager.valueChanged)
Spacer()
Group
Text("American currency")
TextField(currencyManagerUS.string, text: $currencyManagerUS.string)
.keyboardType(.numberPad)
.multilineTextAlignment(.trailing)
.onChange(of: currencyManagerUS.string, perform: currencyManagerUS.valueChanged)
Spacer()
Group
Text("British currency")
TextField(currencyManagerUK.string, text: $currencyManagerUK.string)
.keyboardType(.numberPad)
.multilineTextAlignment(.trailing)
.onChange(of: currencyManagerUK.string, perform: currencyManagerUK.valueChanged)
Spacer()
Group
Text("French currency")
TextField(currencyManagerFR.string, text: $currencyManagerFR.string)
.keyboardType(.numberPad)
.multilineTextAlignment(.trailing)
.onChange(of: currencyManagerFR.string, perform: currencyManagerFR.valueChanged)
Spacer()
Group
Text("Brazilian currency")
TextField(currencyManagerBR.string, text: $currencyManagerBR.string)
.keyboardType(.numberPad)
.multilineTextAlignment(.trailing)
.onChange(of: currencyManagerBR.string, perform: currencyManagerBR.valueChanged)
Spacer()
.padding(.trailing, 25)
struct ContentView_Previews: PreviewProvider
static var previews: some View
ContentView()
class CurrencyManager: ObservableObject
@Published var string: String = ""
private var amount: Decimal = .zero
private let formatter = NumberFormatter(numberStyle: .currency)
private var maximum: Decimal = 999_999_999.99
private var lastValue: String = ""
init(amount: Decimal, maximum: Decimal = 999_999_999.99, locale: Locale = .current)
formatter.locale = locale
self.string = formatter.string(for: amount) ?? "$0.00"
self.lastValue = string
self.amount = amount
self.maximum = maximum
func valueChanged(_ value: String)
let newValue = (value.decimal ?? .zero) / pow(10, formatter.maximumFractionDigits)
if newValue > maximum
string = lastValue
else
string = formatter.string(for: newValue) ?? "$0.00"
lastValue = string
extension NumberFormatter
convenience init(numberStyle: Style, locale: Locale = .current)
self.init()
self.locale = locale
self.numberStyle = numberStyle
extension Character
var isDigit: Bool "0"..."9" ~= self
extension LosslessStringConvertible
var string: String .init(self)
extension StringProtocol where Self: RangeReplaceableCollection
var digits: Self filter (\.isDigit)
var decimal: Decimal? Decimal(string: digits.string)
这是相当于我为 UIKit 实现的自定义 CurrencyField 的 SwiftUI。
【讨论】:
@fs_tigre Btw AFAIR 100 在将最大值设置为 100 时也能正常工作。IMO SwiftUI 仍然缺乏很多功能,最终我们不得不依赖 UIKit 来获得更高级的功能。 @fs_tigre 不幸的是,没有办法使用 SwiftUI AFAIK 检测 deleteBackwards,但您可以尝试检测比较 lastValue 和 current。 @fs_tigre 尝试在valueChanged
方法中添加这个 var value = value
for index in lastValue.indices
if value == lastValue[..<index] + lastValue[lastValue.index(after: index)...]
value = lastValue.digits.dropLast().string
break
这将强制删除最后一个数字,无论光标/插入符号位于何处
@LeoDabus 成功了!你就是那个男人。非常感谢。【参考方案2】:
我创建了一个环绕 UITextfield 的组件。
你可以在这里查看https://github.com/youjinp/SwiftUIKit
这是演示
【讨论】:
这不像 OP 要求的那样工作。 OP 想要从右到左输入值。它也不显示$0.00
。以上是关于如何让用户在 SwiftUI 文本字段中仅使用数字输入货币,同时保留 $ 和 .?的主要内容,如果未能解决你的问题,请参考以下文章