0

I'm learning how to code using the tutorial provided by Apple. I'm at the stage where I learn to pass data with bindings.

I'm still learning so please expect two noob questions below, I've tried to find answers to this but couldn't find anything compelling, hence this post. Thanks in advance for your patience :)

  1. Summarize the problem The example provided in the tutorial makes use of TextField to allow user inputs. I'm trying to do that so that people can enter a year (2008) for example, however, that doesn't work because the data I'm trying to pass it to is a Double, so I get an error message. I've tried to use the date picker as well, but that leads me to deal with dates, which seems very complex to me at this stage.

  2. Describe what you’ve tried I've tried to make it so that the user input from the TextField is considered as a Double, but every way I tried it leads to more error messages. Nothing has worked so far.

  3. When appropriate, show some code:

My data model:

struct Animal: Identifiable {
    let id: UUID
    var name: String
    var birthyear: Int
 }
  
init(id: UUID = UUID(), name: String, birthyear: Int){
        self.id = id
        self.name = name
        self.birthyear = birthyear
}

extension Animal {
    static var data: [Animal] {
        [
            Animal(name: "Félix", birthyear: 1999
         ]
    }
}

extension Animal {
    struct Data {
        var name: String = ""
        var birthyear: Double = 1999       
    }
    
    var data: Data {
        return Data (name: name, birthyear: Double(birthyear))
    }
    
    mutating func update(from data: Data) {
        name = data.name
        birthyear = Int(data.birthyear)
    }
}

and the code where my TextField appears (the EditView):

import SwiftUI

struct EditView: View {
@Binding var animData: Animal.Data

var body: some View {
    Form {
        Section(header: Text("Animal Info")){
            List {
                TextField("Title", text: $animData.name)
                HStack {
                    TextField("Birth Year", text: $animData.vaccinyear)
                    }
                }
            }
        }
    }

struct EditView_Previews: PreviewProvider {
    static var previews: some View {
        EditView(animData: .constant(Animal.data[0].data))
    }
}

Now I have two questions:

If I want the data passed as a Double, so my code above would actually work, what should I do? It works with a slider, but it is a ridiculous way to go about it.

(the slider code that works)

   var body: some View {
        Form {
            Section(header: Text("Animal Info")){
                List {
                    TextField("Title", text: $animData.name)
                    HStack {
                        Slider(value: $animData.birthyear, in: 1999...2021, step: 1.0) {
                            Text("Length")
                        }
            }
       }
  }

edit: solution from here: SwiftUI - How to create TextField that only accepts numbers

TextField("Birth Year", text: $animData.birthyear)
                        .keyboardType(.numberPad)
                        .onReceive(Just(animData.birthyear)) { newValue in
                            let filtered = newValue.filter { "0123456789".contains($0) }
                            if filtered != newValue {
                                self.animData.birthyear = filtered
                            }
                        }
Joe
  • 401
  • 1
  • 3
  • 9
  • Why are you wanting a `Double` (which is a decimal value) and not an `Int` for a year value? If you should be using `Int` (which is what I suspect), is the crux of the question how to get an `Int` binding from a `TextField`? – jnpdx Apr 24 '21 at 16:25
  • Int is probably more appropriate indeed! And the question it raises with it. But your message made me think that I should take the problem the other way around and declare bithyear not as Double, nor Int but String, and that way it is compatible with TextField, for which I can just add .keyboardType(.decimalPad) and that'll do the job. (https://www.hackingwithswift.com/books/ios-swiftui/reading-text-from-the-user-with-textfield) – Joe Apr 24 '21 at 18:13
  • There are plenty of questions here on SO about using TextField with number values. I would suggest *not* defining it as `String`, since that's not actually the value type you want. And, `.decimalPad` won't prevent users from pasting other string values in. – jnpdx Apr 24 '21 at 18:19
  • The suggestion of Mr. John M. to sanitize the inputs seem to work on my code, still keeping the String at this point, as I have found no valid solution to do it differently: https://stackoverflow.com/questions/58733003/swiftui-how-to-create-textfield-that-only-accepts-numbers – Joe Apr 24 '21 at 19:14

1 Answers1

0

If you're interested in a solution that still lets you keep an Int, this modification works by using an intermediary @State variable with the String value and then assigns the real Int value if it is valid.

struct ContentView : View {
    @State var birthyear = 1999
    @State var stringBirthyear = ""
    
    var birthyearBinding : Binding<String> {
        .init {
            print("returning",birthyear)
            return "\(birthyear)"
        } set: { (newValue) in
            if let intVal = Int(newValue) {
                DispatchQueue.main.async {
                    print("Setting birthyear to",intVal)
                    self.birthyear = intVal
                }
            }
        }
    }
    
    var body: some View {
        Text("\(birthyear)")
        TextField("Birth Year", text: $stringBirthyear)
            .keyboardType(.numberPad)
            .onReceive(Just(stringBirthyear)) { newValue in
                let filtered = newValue.filter { "0123456789".contains($0) }
                if filtered != newValue {
                    print(filtered,newValue)
                    stringBirthyear = filtered
                }
                if let intVal = Int(filtered) {
                    birthyear = intVal
                }
            }
            .onAppear {
                stringBirthyear = "\(birthyear)"
            }
    }
}
jnpdx
  • 45,847
  • 6
  • 64
  • 94