14

I am building some basic form functionality in my app at the moment and I am having trouble with TextFields not changing the value in the relevant binded variable when tapping another TextField or pressing "Done" in Edit Mode.

    @Binding var jobDetails: JobDetails
    @Environment(\.colorScheme) var colorScheme: ColorScheme

    ...

    var body: some View {

       ...
                        HStack {
                            Text("Hourly Rate")
                            Spacer()
                            TextField("", value: $jobDetails.hourlyRateBasic, formatter: TextFormatters().currencyFormatter())

                                .keyboardType(.asciiCapableNumberPad)
                                .multilineTextAlignment(.trailing)
       ...

In the iOS simulator, the field only seems to update when I physically hit the return key on my keyboard after typing in a new value (not the soft keyboard in the simulator). I would like the TextField to commit it's change to jobDetails.hourlyRateBasic when tapping another TextField or pressing "Done" to exit edit mode.

It seems that onEditingChanged fires when I tap another TextField, but I don't know how to leverage that into changing the jobDetails with the new value.

rmaddy
  • 314,917
  • 42
  • 532
  • 579
James Woodcock
  • 429
  • 1
  • 7
  • 17
  • ‘JobDetails’ is a struct so I don’t believe I can do what you’re suggesting. The type of .hourlyRateBasic is Double? as I’ve seen is required in some other resources. The updating of jobDetails works perfectly when pressing enter on the TextField after editing, which is why I am confused as to it not updating. jobDetails has another property that is a String and the updating works fine for this property. I will edit my answer in the morning to include more code. Thanks for your help. – James Woodcock Aug 07 '19 at 21:38
  • 4
    I have seen this too and I think the root issue is that _for TextFields that supply a formatter_, the formatter is only getting called when return is hit. If no formatter is supplied, the `TextField`'s value is updated with each keypress. And since the value is not updated, I don't see any way of getting the user's input even in `onEditingChanged`. Once again, it's a SwiftUI bug we have to hope will be fixed soon. – KRH Aug 08 '19 at 00:24
  • 1
    @KRH is right about the formatter. It seems to me that it is indeed a bug. I put a breakpoint in the formatter's `isPartialStringValid(...)` method, and it is never called. You should submit a bug report. – kontiki Aug 08 '19 at 04:17
  • 1
    Thank you both, I had a feeling that this might be the case. I will file a bug report and hope it is fixed in beta 6, and add an answer when it is resolved. – James Woodcock Aug 08 '19 at 08:08
  • 1
    Not to continue the witch hunt here. But I am having the same issue with stored Double (ie. 4.87). When I render on my phone it looks just fine. However, when I click on the text fields to edit the Double Value; the value just persists and never gets updated. – Clayton Allen Sep 28 '19 at 20:39
  • I am having the same problem using XCode 12. I see that you have accepted an answer, but I can’t see how that has helped. Did yo manage to get the above example working? – Manngo Sep 19 '20 at 02:29
  • I have a solution which involves creating a simple `IntField`, which takes about a dozen lines of code. See https://stackoverflow.com/questions/56799456/swiftui-textfield-with-formatter-not-working/63967389#answer-63967389 – Manngo Sep 19 '20 at 09:53

2 Answers2

1

This is typical behavior of TextField in SwiftUI. Following is an example of it and alternative method to make TextField more responsive while typing.

import SwiftUI

struct ContentView: View {
    @State private var text: String = "0"
    @State private var num: Int = 0
    private var resultString: String {
        if let num = Int(self.text) {
            return String(num*num)
        }
        return "0"
    }
    private var resultInt: Int {
        return self.num*self.num
    }
    var body: some View {
        VStack {
            VStack(alignment:.leading) {
                Text("Input number as String")
                TextField("String Number",text: self.$text)
                .textFieldStyle(RoundedBorderTextFieldStyle())
                Text("Input number as Int")
                TextField("Int Number", value: self.$num, formatter: NumberFormatter())
                .textFieldStyle(RoundedBorderTextFieldStyle())
            }
            Spacer()
            Text("From String")
            Text("Square of \(self.text) is \(self.resultString)") .font(.title)
            Spacer()
            Text("From Int")
            Text("Square of \(self.num) is \(self.resultInt)") .font(.title)
            Spacer()
        }.padding()
    }
}
NikzJon
  • 912
  • 7
  • 25
  • 6
    Does this actually answer the question? I can’t see how this helps in committing the change on the number field. – Manngo Sep 19 '20 at 02:28
1

This is already fixed. Tested with Xcode 13.3 / iOS 15.4

demo

struct TestView: View {
    @State private var value1 = 1.0
    @State private var text = ""

    private var currencyFormatter: NumberFormatter = {
         var nf = NumberFormatter()
         nf.numberStyle = .currency
         return nf
    }()

    var body: some View {
        VStack {
            HStack {
                Text("Hourly Rate [\(value1)]")
                Spacer()
                TextField("", value: $value1, formatter: currencyFormatter)

                    .keyboardType(.asciiCapableNumberPad)
                    .multilineTextAlignment(.trailing)
            }
            HStack {
                Text("Other")
                Spacer()
                TextField("Enter something", text: $text)
            }
        }
    }
}
Asperi
  • 228,894
  • 20
  • 464
  • 690