'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()
    }
}

backup

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