Skip to content

Instantly share code, notes, and snippets.

@darrarski
Last active October 18, 2023 18:10
Show Gist options
  • Save darrarski/b500960c857b403bfff25228b12aadef to your computer and use it in GitHub Desktop.
Save darrarski/b500960c857b403bfff25228b12aadef to your computer and use it in GitHub Desktop.
SwiftUI NumberField - TextField with custom display/edit number formatters
import SwiftUI
public struct NumberField: View {
public init(_ title: String,
value: Binding<NSNumber?>,
decorator: @escaping (String) -> String = { $0 }) {
self.title = title
self.value = value
let formatter = NumberFormatter()
formatter.maximumFractionDigits = -1
self.displayFormatter = formatter
self.editingFormatter = formatter
self.decorator = decorator
}
public init(_ title: String,
value: Binding<NSNumber?>,
formatter: NumberFormatter,
decorator: @escaping (String) -> String = { $0 }) {
self.title = title
self.value = value
self.displayFormatter = formatter
self.editingFormatter = formatter
self.decorator = decorator
}
public init(_ title: String,
value: Binding<NSNumber?>,
displayFormatter: NumberFormatter,
editingFormatter: NumberFormatter,
decorator: @escaping (String) -> String = { $0 }) {
self.title = title
self.value = value
self.displayFormatter = displayFormatter
self.editingFormatter = editingFormatter
self.decorator = decorator
}
public var body: some View {
TextField(title, text: Binding(get: {
self.isEditing ? self.editingValue : self.formattedValue
}, set: { newValue in
self.editingValue = newValue
self.updateValue(with: newValue)
}), onEditingChanged: { isEditing in
self.isEditing = isEditing
self.editingValue = self.formattedValue
})
}
let title: String
let value: Binding<NSNumber?>
let displayFormatter: NumberFormatter
let editingFormatter: NumberFormatter
let decorator: (String) -> String
@State private var editingValue: String = ""
@State private var isEditing: Bool = false
private var formattedValue: String {
guard let value = self.value.wrappedValue else { return "" }
let formatter = isEditing ? editingFormatter : displayFormatter
guard let formattedValue = formatter.string(for: value) else { return "" }
return isEditing ? formattedValue : decorator(formattedValue)
}
private func updateValue(with string: String) {
let newValue = editingFormatter.number(from: string)
let newString = newValue.map { editingFormatter.string(for: $0) } as? String
value.wrappedValue = newString.map { editingFormatter.number(from: $0) } as? NSNumber
}
}
import SwiftUI
struct ExampleView: View {
@State private var amount: Decimal?
var body: some View {
Form {
NumberField(
"Enter amount",
value: Binding(get: {
self.amount.map { NSDecimalNumber(decimal: $0) }
}, set: { number in
self.amount = number?.decimalValue
}),
displayFormatter: .currency,
editingFormatter: .currencyEditing,
decorator: { "[ \($0) ]" }
)
Text("Amount: \(String(describing: amount))")
}
}
}
extension NumberFormatter {
static let currency: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .currency
return formatter
}()
static let currencyEditing: NumberFormatter = {
let formatter = NumberFormatter()
formatter.numberStyle = .decimal
formatter.groupingSeparator = ""
formatter.minimumFractionDigits = NumberFormatter.currency.minimumFractionDigits
formatter.maximumFractionDigits = NumberFormatter.currency.maximumFractionDigits
return formatter
}()
}
@gmoraleda
Copy link

Makes sense.
Thanks for the reply (and for the snippet!).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment