FYI: Stweart Lynch just posted his awesomely crafted version of a number only TextField. If you are looking for a best solution - use his!
I'm currently working on TextField custom modifiers for formatting input/output of numbers.
If you have any articles/videos to recommend - I'm eager to learn the better practices.
The green text field for 10 shares with a clear (X) button is what I've gotten to so far.
I'm thinking the (X) clear button may look better on the left and the text right aligned. This is next...
But most of the code is working. I've got an issue with the clearing of the field... I believe it comes from passing yet another value into the modifier - instead of using the internal field's value.
// see: https://developer.apple.com/documentation/swiftui/reducing-view-modifier-maintenance
import SwiftUI
struct Preview_CustomNumberFieldButton: PreviewProvider {
@State static var number = 12.7
@State static var one = 1.0
static var previews: some View {
VStack {
NumberFieldButton( value: $one, actionType: .confirm )
NumberFieldButton( value: $number, actionType: .confirm, units: "shares")
.keyboardType(.numberPad)
Text("Value of number State var is: \(number)")
}
.padding()
}
}
struct NumberFieldButton: View {
@Binding var value: Double
@FocusState var isTextFieldFocused: Bool
var actionType: Action
var units: String = "shares"
var body: some View {
HStack {
TextField("shares", value: $value, format: .number )
.textFieldStyle(.roundedBorder)
.focused($isTextFieldFocused)
.showNumberClearButton($value)
Text(units)
}
.padding()
.background(
RoundedRectangle(cornerRadius: 8, style: .continuous)
.fill(actionType.bgColor)
.overlay(
RoundedRectangle(cornerRadius: 8, style: .continuous)
.stroke(actionType.stroke)
)
)
.foregroundColor(actionType.fgColor)
}
enum Action {
case confirm, cancel, delete
var bgColor: Color {
switch self {
case .confirm:
return Color("CTA_Primary")
case .cancel:
return Color("CTA_Quaternary")
case .delete:
return Color("CTA_Secondary")
}
}
var fgColor: Color {
if self == .cancel {
return Color(.label)
// UIColor.label used in init
} else {
return Color.black
}
}
var stroke: Color {
if self == .cancel {
return fgColor
} else {
return bgColor
}
}
var image: Image {
switch self {
case .confirm:
return Image(systemName: "checkmark.rectangle.fill")
case .cancel:
return Image(systemName: "clear.fill")
case .delete:
return Image(systemName: "trash")
}
}
}
}
struct NumberFieldClearButton: ViewModifier {
@Binding var fieldNumber: Double
func body(content: Content) -> some View {
content
.overlay {
HStack {
Spacer()
Button {
fieldNumber = 0.0 // action is to clear value
} label: {
Image(systemName: "multiply.circle.fill")
}
.foregroundColor(.secondary)
.padding(.trailing, 4)
}
}
}
}
extension View {
// FIXME: does not handle value change without the return key well
func showNumberClearButton(_ value: Binding<Double>) -> some View {
ModifiedContent(content: self, modifier: NumberFieldClearButton(fieldNumber: value))
// self.modifier(NumberFieldClearButton(fieldNumber: value))
}
}
One feature I want to add... I would like to swap the number and the clear button. I want the number on the right - like in a calculator input screen and the clear button on the left.
See Also:
How to format a TextField for numbers by Paul Hudson
Apple's Doc Configuring Views & Reducing view modifier maintenance
Stweart Lynch - Numeric TextFields in SwiftUI - YouTube
A version combining Stweart's modifier and putting the clear button on the left with number on the right (like a calculator display).