Use Binding<Int> with a TextField SwiftUI
Solution 1
Actually , you can binding manyTypes with TextField
:
@State var weight: Int = 0
var body: some View {
Group{
Text("\(weight)")
TextField("Weight", value: $weight, formatter: NumberFormatter())
}}
Solution 2
Of course it is possible to use
TextField("", value: $value, formatter: NumberFormatter())
// .keyboardType(UIKeyboardType.decimalPad) // << uncomment for num pad
and even with Numeric Pad, but this does not prevent to enter non-numeric characters into such TextField
, and until commit formatter
is not called to validate input. Maybe Apple will give us possibility to validate input on the fly in future, but not now, ... so I prefer different way
Here is my approach to have text field for numeric values (Int, Float, Double, etc.) which validates input and limits of specific type (say do not allow to enter values longer then fit into Int maximum allowed value). Hope it would be helpful for someone as well. (Of course configurations like font, size, colors, etc. are possible per usage needs)
struct NumberTextField<V>: UIViewRepresentable where V: Numeric & LosslessStringConvertible {
@Binding var value: V
typealias UIViewType = UITextField
func makeUIView(context: UIViewRepresentableContext<NumberTextField>) -> UITextField {
let editField = UITextField()
editField.delegate = context.coordinator
return editField
}
func updateUIView(_ editField: UITextField, context: UIViewRepresentableContext<NumberTextField>) {
editField.text = String(value)
}
func makeCoordinator() -> NumberTextField.Coordinator {
Coordinator(value: $value)
}
class Coordinator: NSObject, UITextFieldDelegate {
var value: Binding<V>
init(value: Binding<V>) {
self.value = value
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
let text = textField.text as NSString?
let newValue = text?.replacingCharacters(in: range, with: string)
if let number = V(newValue ?? "0") {
self.value.wrappedValue = number
return true
} else {
if nil == newValue || newValue!.isEmpty {
self.value.wrappedValue = 0
}
return false
}
}
func textFieldDidEndEditing(_ textField: UITextField, reason: UITextField.DidEndEditingReason) {
if reason == .committed {
textField.resignFirstResponder()
}
}
}
}
struct TestTextFieldWithNumbers: View {
@State private var value = 0
var body: some View {
VStack {
Text("Current value: \(value)")
Divider()
TextField("", value: $value, formatter: NumberFormatter())
// .keyboardType(UIKeyboardType.decimalPad)
Divider()
NumberTextField(value: $value)
.frame(height: 32)
}
}
}
struct TestTextFieldWithNumbers_Previews: PreviewProvider {
static var previews: some View {
TestTextFieldWithNumbers()
}
}
Solution 3
Just to make it more observable.
change this:
TextField("", text: $intvalue)
to that
TextField("Value", value: $amount, formatter: NumberFormatter())
Bernard
Updated on July 09, 2022Comments
-
Bernard almost 2 years
I am currently building a page to add player information to a local database. I have a collection of TextFields for each input which is linked to elements in a player struct.
var body: some View { VStack { TextField("First Name", text: $player.FirstName) .textFieldStyle(RoundedBorderTextFieldStyle()) TextField("Last Name", text: $player.LastName) .textFieldStyle(RoundedBorderTextFieldStyle()) TextField("Email", text: $player.eMail) .textFieldStyle(RoundedBorderTextFieldStyle()) TextField("Shirt Number", text: $player.ShirtNumber) .textFieldStyle(RoundedBorderTextFieldStyle()) TextField("NickName", text: $player.NickName) .textFieldStyle(RoundedBorderTextFieldStyle()) TextField("Height", text: $player.Height) .textFieldStyle(RoundedBorderTextFieldStyle()) TextField("Weight", text: $player.Weight) .textFieldStyle(RoundedBorderTextFieldStyle()) Button(action: { submitPlayer(player: self.player)T }) { Text("Submit") } Spacer() } }
My player struct is
struct Player: Hashable, Codable, Identifiable { var id: Int var FirstName: String var LastName: String var NickName: String var eMail: String var ShirtNumber: Int var Height: Int var Weight: Int }
The issue is that ShirtNumber, Height, and Weight are all Int values. When I bind them to the TextField I get an error saying
Cannot convert value of type 'Binding<Int>' to expected argument type 'Binding<String>'
. Everything I have looked into about SwiftUI says it's impossible to have a TextField with an Int value bound to it.My question is, would it be possible to create a new class that extends TextField but allows for only Int inputs and that binds an Int variable, something like this?
struct IntTextField: TextField { init(_ text: String, binding: Binding<Int>) { } }
So far all I have been able to find is an answer to part of my question (accepting only Int input) from this question. I am looking for a way to combine this with the
Binding<Int>
.Thanks for the help.