Last active
September 26, 2024 06:33
-
-
Save darrarski/065520cc7095a7e0e33e72373feab3ac to your computer and use it in GitHub Desktop.
SwiftUI FormattedTextField - TextField with custom display/edit formatters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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