Thank you to workingdog for what was almost the answer. It did not work well with formatter, but it gave me the idea that did work.
Formatting right inside the binding doesn't work because the cursor sometimes jumps around after formatting.
First my cash formatter:
let cashFormat = getCashFormat()
func getCashFormat() -> NumberFormatter {
let thisCashFormat = NumberFormatter()
thisCashFormat.numberStyle = NumberFormatter.Style.currency
thisCashFormat.roundingMode = NumberFormatter.RoundingMode.halfEven
// halfEven rounding is sometimes referred to as Bankers’ rounding.
thisCashFormat.maximumFractionDigits = 2
return thisCashFormat
}
cashFormat
formatting will return a nil if the string doesn't have a $ as the first character. I need another NumberFormatter
.
let basicFormat = getBasicFormat()
func getBasicFormat() -> NumberFormatter {
let thisBasicFormat = NumberFormatter()
thisBasicFormat.numberStyle = NumberFormatter.Style.decimal
thisBasicFormat.roundingMode = NumberFormatter.RoundingMode.halfEven
thisBasicFormat.minimumFractionDigits = 0
thisBasicFormat.maximumFractionDigits = 6
return thisBasicFormat
}
basicFormat
returns more fraction digits than I need, but I'll be converting the double into integer cents elsewhere in my app before the model stores it.
I created an ObservableObject
class for the formatting.
class FieldFormatter: ObservableObject {
@Binding var dollars: Double
@Published var dollarText: String
init(dollars: Binding<Double>) {
self._dollars = dollars
self.dollarText = dollars.wrappedValue.cash()
}
func dollarsChanged() {
if let thisNumber = cashFormat.number(from: dollarText) {
self.dollars = thisNumber.doubleValue
} else if let thisNumber = basicFormat.number(from: dollarText) {
self.dollars = thisNumber.doubleValue
}
}
}
Finally, here's my TextField struct.
struct CashFieldC: View {
var thisLabel: String
@Binding var dollars: Double
@ObservedObject var fieldFormatter: FieldFormatter
init(thisLabel: String, dollars: Binding<Double>) {
self.thisLabel = thisLabel
self._dollars = dollars
self.fieldFormatter = FieldFormatter(dollars: dollars)
}
var body: some View {
VStack(alignment: .leading) {
Text(thisLabel).font(.caption)
TextField(thisLabel, text: $fieldFormatter.dollarText, onEditingChanged: { (oec) in
if !oec {
self.fieldFormatter.dollarsChanged()
}
})
.textFieldStyle(RoundedBorderTextFieldStyle())
.keyboardType(.numbersAndPunctuation)
}
}
}
The dollarText
value is set when FieldFormatter
object is initialized. The binding in the TextField
updates it every time a character is entered.
The bound variable in a formatting field only gets updated when the field is committed on hitting return.
Formatting inside a Binding close to workingdog's answer would cause the cursor to jump around after entering each digit.
This struct uses the onEditingChanged:
closure to update the dollars
binding when the field looses focus from hitting return or when selecting another field. The closure will attempt a cash formatting first or a basic double formatting if the cash formatting fails.