Q:

NumberField or how to make TextField input a Double, Float or other numbers with dot

According to comment in this question I made a custom SwifUI View based on a TextField. It use numeric keypad, you can't input there nothing but numbers and point, there can be only one point (dot), and you can pass a Bindable Double @State value through the View for input.
But there is a bug: when you deleting a last zero in "xxx.0" - zero still comes out. When you deleting a dot - zero becomes a part of integer, so it goes to "xxx0"

Any idea how to fix it? I tried to make value an integer when deleting last number before dot - but I can't catch the moment when there is only one last dot in a string.
here's full code:
import SwiftUI
import Combine

struct DecimalTextField: View {
    public let placeHolder: String
    @Binding var numericValue: Double
    private class DecimalTextFieldViewModel: ObservableObject {
        @Published var text = ""{
            didSet{
                DispatchQueue.main.async {
                    let substring = self.text.split(separator: Character("."), maxSplits: 2)
                    switch substring.count{
                        case 0:
                            if self.numericValue != 0{
                                self.numericValue = 0
                            }
                        case 1 :
                            var newValue: Double = 0
                            if let lastChar = substring[0].last{
                                if lastChar == Character("."){
                                    newValue = Double(String(substring[0]).dropLast()) ?? 0
                                }else{
                                    newValue = Double(String(substring[0])) ?? 0
                                }
                            }
                            self.numericValue = newValue
                        default:
                            self.numericValue =  Double(String("\(String(substring[0])).\(String(substring[1]))")) ?? 0
                    }
                }
            }
        }

        private var subCancellable: AnyCancellable!
        private var validCharSet = CharacterSet(charactersIn: "1234567890.")
        @Binding private var numericValue: Double{
            didSet{
                DispatchQueue.main.async {
                    if String(self.numericValue) != self.text {
                        self.text = String(self.numericValue)
                    }
                }
            }
        }
        init(numericValue: Binding<Double>, text: String) {
            self.text = text
            self._numericValue = numericValue
            subCancellable = $text.sink { val in
                //check if the new string contains any invalid characters
                if val.rangeOfCharacter(from: self.validCharSet.inverted) != nil {
                    //clean the string (do this on the main thread to avoid overlapping with the current ContentView update cycle)
                    DispatchQueue.main.async {
                        self.text = String(self.text.unicodeScalars.filter {
                            self.validCharSet.contains($0)
                        })
                    }
                }
            }
        }

        deinit {
            subCancellable.cancel()
        }
    }

    @ObservedObject private var viewModel: DecimalTextFieldViewModel

    init(placeHolder: String = "", numericValue: Binding<Double>){
        self._numericValue = numericValue
        self.placeHolder = placeHolder
        self.viewModel  = DecimalTextFieldViewModel(numericValue: self._numericValue, text: numericValue.wrappedValue == Double.zero ? "" : String(numericValue.wrappedValue))

    }
    var body: some View {
        TextField(placeHolder, text: $viewModel.text)
            .keyboardType(.decimalPad)
    }
}

struct testView: View{
    @State var numeric: Double = 0
    var body: some View{
        return VStack(alignment: .center){
            Text("input: \(String(numeric))")
            DecimalTextField(placeHolder: "123", numericValue: $numeric)
        }
    }
}

struct decimalTextField_Previews: PreviewProvider {
    static var previews: some View {
        testView()
    }
}

A:

The only way to avoid loss of the decimal point and trailing 0's while typing is to keep track of the integral and fractional digits of the string across invocations of the numeric field, which means keeping the digit precision values as part of the state of the superview.  See this gist for a fully functional (Swift 5) view that uses this technique.  To see what happens if you don't keep the digit precision in the superview, compare the behavior of the first and second fields in the preview below: the first will handle typing as expected, the second will remove any trailing .0 as soon as the value changes.

