I've tried to implement Currency TextField to my project, which I found on this website - https://benoitpasquier.com/currency-textfield-in-swiftui.
It almost does work as expected, but with one major issue: passed value is not showing up in TextField. (eg. Binding = 1000 is still presented as $0.00 in TextField)
Code below:
import Foundation
import SwiftUI
import UIKit
class CurrencyUITextField: UITextField {
@Binding private var value: Int
private let formatter: NumberFormatterProtocol
init(formatter: NumberFormatterProtocol, value: Binding<Int>) {
self.formatter = formatter
self._value = value
super.init(frame: .zero)
setupViews()
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func willMove(toSuperview newSuperview: UIView?) {
super.willMove(toSuperview: superview)
addTarget(self, action: #selector(editingChanged), for: .editingChanged)
addTarget(self, action: #selector(resetSelection), for: .allTouchEvents)
keyboardType = .numberPad
textAlignment = .right
sendActions(for: .editingChanged)
}
override func removeFromSuperview() {
Log.info("Removed CurrencyTextField from View")
}
override func deleteBackward() {
text = textValue.digits.dropLast().string
sendActions(for: .editingChanged)
}
private func setupViews() {
tintColor = .clear
font = .systemFont(ofSize: 40, weight: .regular)
}
@objc private func editingChanged() {
text = currency(from: decimal)
resetSelection()
updateValue()
}
@objc private func resetSelection() {
selectedTextRange = textRange(from: endOfDocument, to: endOfDocument)
}
private func updateValue() {
DispatchQueue.main.async { [weak self] in
self?.value = self?.intValue ?? 0
}
}
private var textValue: String {
return text ?? ""
}
private var decimal: Decimal {
return textValue.decimal / pow(10, formatter.maximumFractionDigits)
}
private var intValue: Int {
return NSDecimalNumber(decimal: decimal * 100).intValue
}
private func currency(from decimal: Decimal) -> String {
return formatter.string(for: decimal) ?? ""
}
}
extension StringProtocol where Self: RangeReplaceableCollection {
var digits: Self { filter(\.isWholeNumber) }
}
extension String {
var decimal: Decimal { Decimal(string: digits) ?? 0 }
}
extension LosslessStringConvertible {
var string: String { .init(self) }
}
import SwiftUI
import UIKit
struct CurrencyTextField: View {
@Binding var value: Int
var body: some View {
ZStack {
RoundedRectangle(cornerRadius: 15)
.foregroundColor(.quaternary)
.frame(maxHeight: 80)
CurrencyTextField_(numberFormatter: NumberFormatter.currencyFormatter, value: $value)
.padding(.horizontal)
}
.frame(maxHeight: 80)
}
}
struct CurrencyTextField_: UIViewRepresentable {
typealias UIViewType = CurrencyUITextField
let numberFormatter: NumberFormatterProtocol
let currencyField: CurrencyUITextField
init(numberFormatter: NumberFormatterProtocol, value: Binding<Int>) {
self.numberFormatter = numberFormatter
currencyField = CurrencyUITextField(formatter: numberFormatter, value: value)
}
func makeUIView(context: Context) -> CurrencyUITextField {
return currencyField
}
func updateUIView(_ uiView: CurrencyUITextField, context: Context) {}
}
struct CurrencyTextField_Previews: PreviewProvider {
static var previews: some View {
CurrencyTextField(value: .constant(1025))
}
}
This view can be initialised with amount = 0, or as an 'Edit' view, with injected amount value (eg. 1000, previously created by the same CurrencyTextField)
struct CreateTransactionView: View {
@State var amount = 0
var body: some View {
VStack(alignment: .leading, spacing: 15) {
CurrencyTextField(value: $amount)
}
}
}
What could be possibly causing this issue? PS. I have little to no experience with UIKit.
CodePudding user response:
updateUIView
hasn't been implemented. And makeUIView
isn't done correctly.
CodePudding user response:
You can use currency in TextField
by changing the format which is only available for iOS 15 .
@available(macOS 12.0, iOS 15.0, tvOS 15.0, watchOS 8.0, *)
extension FormatStyle {
public static func currency<Value>(code: String) -> Self where Self == FloatingPointFormatStyle<Value>.Currency, Value : BinaryFloatingPoint
}
struct ContentView: View {
@State private var myMoney: Double? = 300.0
var body: some View {
TextField(
"Currency (USD)",
value: $myMoney,
format: .currency(code: "USD")
)
}
}
or using Locale
struct ContentView: View {
private let locale = Locale.current
@State private var myMoney: Double? = 1000.0
var body: some View {
TextField(
"Currency (\(locale.currencyCode ?? "USD")",
value: $myMoney,
format: .currency(code: locale.currencyCode ?? "USD")
)
}
}