SwiftUI TextField & Modifiers

FYI: Stweart Lynch just posted his awesomely crafted version of a number only TextField. If you are looking for a best solution - use his!

Restrict TextField to Numbers

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.

CalcTextField

A version combining Stweart's modifier and putting the clear button on the left with number on the right (like a calculator display).