Skip to content

Instantly share code, notes, and snippets.

@darrarski
Last active September 26, 2024 06:33
Show Gist options
  • Save darrarski/065520cc7095a7e0e33e72373feab3ac to your computer and use it in GitHub Desktop.
Save darrarski/065520cc7095a7e0e33e72373feab3ac to your computer and use it in GitHub Desktop.
SwiftUI FormattedTextField - TextField with custom display/edit formatters
import SwiftUI
public struct FormattedTextField<Formatter: TextFieldFormatter>: View {
public init(_ title: String,
value: Binding<Formatter.Value>,
formatter: Formatter) {
self.title = title
self.value = value
self.formatter = formatter
}
let title: String
let value: Binding<Formatter.Value>
let formatter: Formatter
public var body: some View {
TextField(title, text: Binding(get: {
if self.isEditing {
return self.editingValue
} else {
return self.formatter.displayString(for: self.value.wrappedValue)
}
}, set: { string in
self.editingValue = string
self.value.wrappedValue = self.formatter.value(from: string)
}), onEditingChanged: { isEditing in
self.isEditing = isEditing
self.editingValue = self.formatter.editingString(for: self.value.wrappedValue)
})
}
@State private var isEditing: Bool = false
@State private var editingValue: String = ""
}
public protocol TextFieldFormatter {
associatedtype Value
func displayString(for value: Value) -> String
func editingString(for value: Value) -> String
func value(from string: String) -> Value
}
import SwiftUI
struct ExampleView: View {
@State private var amount: Decimal?
@State private var date: Date?
var body: some View {
Form {
Section {
FormattedTextField(
"Enter amount",
value: $amount,
formatter: CurrencyTextFieldFormatter()
)
Text("Amount: \(String(describing: amount))")
}
Section {
FormattedTextField(
"Enter date (\(DateFormatter.editing.dateFormat!))",
value: $date,
formatter: DateTextFieldFormatter()
)
Text("Date: \(String(describing: date))")
}
}
}
}
struct CurrencyTextFieldFormatter: TextFieldFormatter {
typealias Value = Decimal?
func displayString(for value: Decimal?) -> String {
guard let value = value else { return "" }
return NumberFormatter.currency.string(for: value) ?? ""
}
func editingString(for value: Decimal?) -> String {
guard let value = value else { return "" }
return NumberFormatter.currencyEditing.string(for: value) ?? ""
}
func value(from string: String) -> Decimal? {
let formatter = NumberFormatter.currencyEditing
let value = formatter.number(from: string)?.decimalValue
let formattedString = value.map { formatter.string(for: $0) } as? String
return formattedString.map { formatter.number(from: $0)?.decimalValue } as? Decimal
}
}
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
}()
}
struct DateTextFieldFormatter: TextFieldFormatter {
typealias Value = Date?
func displayString(for value: Date?) -> String {
guard let value = value else { return "" }
return DateFormatter.display.string(from: value)
}
func editingString(for value: Date?) -> String {
guard let value = value else { return "" }
return DateFormatter.editing.string(from: value)
}
func value(from string: String) -> Date? {
DateFormatter.editing.date(from: string)
}
}
extension DateFormatter {
static let display: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .full
return formatter
}()
static let editing: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .short
return formatter
}()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment