简体   繁体   中英

SwiftUI TextField + Stepper

I'm trying to implement a TextField with a number input together with Stepper to control quantity. After entering a number in the TextField the Stepper loses the ability to change the number. I'm pretty sure there is a trick with Binding value, but can't figure out what exactly.

struct TestView: View {
  @State var quantity: Int = 0

  var body: some View {
    HStack {
      TextField("", value: $quantity, formatter: NumberFormatter())
      Stepper("", onIncrement: {
        self.quantity += 1
      }, onDecrement: {
        self.quantity -= 1
      })
    }
  }
}

This is because using NumberFormatter in TextField is bugged .

You may need to use a custom binding instead:

struct ContentView: View {
    @State var quantity: Int = 0

    static let formatter = NumberFormatter()

    var binding: Binding<String> {
        .init(get: {
            "\(self.quantity)"
        }, set: {
            self.quantity = Int($0) ?? self.quantity
        })
    }

    var body: some View {
        HStack {
            TextField("", text: binding)
            Stepper("", onIncrement: {
                self.quantity += 1
            }, onDecrement: {
                self.quantity -= 1
            })
        }
    }
}

Also don't recreate NumberFormatter every time:

TextField("", value: $quantity, formatter: NumberFormatter())

You can use a static property which is only created once:

static let formatter = NumberFormatter()

I had a similar issue with Stepper and TextField so I decided to make a Swift package that solves the issue.

https://github.com/joe-scotto/TextFieldStepper

After entering a number in the TextField the Stepper loses the ability to change the number.

I was facing this issue too and actually found that the onIncrement and onDecrement actions continue to be accordingly fired. However, I noticed that engaging with the Stepper doesn't release the user focus on the TextField as observed through the continued blinking cursor.

Removing the focus from the TextField while the user engages with the Stepper resolves the issue.

This also makes sense when consulting the TextField documentation:

If the value is a string, the text field updates this value continuously as the user types or otherwise edits the text in the field. For non-string types, it updates the value when the user commits their edits, such as by pressing the Return key.

(Source: https://developer.apple.com/documentation/swiftui/textfield )

As such, I'd recommend simulating user commit events when the user engages with the Stepper . This avoids unnecessarily type-casting and allows you to continue to take advantage of the TextField formatter initialiser.

Example modification of the original code:

struct TestView: View {
  @State var quantity: Int = 0

  // Keep track of which field the user is focused on
  @FocusState private var focusedField: String?

  var body: some View {
    HStack {
      TextField("", value: $quantity, formatter: NumberFormatter())
        .focused($focusedField, equals: "quantity")
      Stepper("", onIncrement: {
        // Remove the focus from the (text) field
        focusedField = nil

        self.quantity += 1
      }, onDecrement: {
        // Remove the focus from the (text) field
        focusedField = nil

        self.quantity -= 1
      })
    }
  }
}

For more information, also see the focused(_:equals:) and @FocusState documentation: https://developer.apple.com/documentation/swiftui/view/focused(_:equals:) https://developer.apple.com/documentation/SwiftUI/FocusState

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM