'Use Binding<Int> with a TextField SwiftUI
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.
Solution 1:[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:[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:[3]
Just to make it more observable.
change this:
TextField("", text: $intvalue)
to that
TextField("Value", value: $amount, formatter: NumberFormatter())
Solution 4:[4]
Was trying to make it work without UIKit since I am trying to build a small macOS app and so UIKit is not present there so found a solution which uses two variables (not the cleanest but works)
Variable declaration:
@State var minutes = 0
@State var minutesString = "0"
TextField and Stepper:
HStack {
TextField("minutes", text: self.$minutesString)
.onReceive(Just(self.minutesString)) { newValue in
let filtered = newValue.filter { $0.isNumber }
if filtered != newValue {
self.minutesString = filtered
self.minutes = Int(filtered) ?? -1
}
}
Stepper("minutes", value: $minutes).onChange(of: self.hours) { newValue in
self.minutesString = String(newValue)
}
.labelsHidden()
}
Which would result in both variables being changed when either the textField or the stepper is being written to, and does not allow the input of anything other than numbers.
I am kinda new to the whole swift thing and so any feedback is greatly appreciated.
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
Solution | Source |
---|---|
Solution 1 | E.Coms |
Solution 2 | |
Solution 3 | SoliQuiD |
Solution 4 | elarcoiris |