Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swift and SwiftUI binding from TextField string to model's published, optional Integer property

I'm new to Swift and SwiftUI and have a model with a @Published intValue optional and a view with a TextField where its text/string property is 2-way bound to intValue of the model. Need to make sure valid text entry as a string is converted to a valid integer for the model. What is the recommended approach to handle this in SwiftUI? Code is similar to below...

//Model
class AppModel:ObservedObject {
 @Published
 var intValue: Int? = 5
}

//View
@ObservedObject
 var appModel = AppModel.shared

TextField("", text: $appModel.intValue)
                    .keyboardType(.numberPad)

Updated sample code...

///////////
import SwiftUI

class Model: ObservableObject {
    static var shared: Model = Model()

    @Published
    var number: Int = 0

    init(){}

}

struct ContentView: View {
    var body: some View {
        TextFieldFormatter()
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}


struct TextFieldFormatter: View {
    @ObservedObject
    var model: Model = Model.shared

    @State private var number: Int = 0


    let formatter: NumberFormatter = {
        let numFormatter = NumberFormatter()
        numFormatter.numberStyle = .none
        return numFormatter
    }()

    var body: some View {
        VStack {
            Text("State").onTapGesture {
                self.endEditing()
            }
            TextField("int", value: $number, formatter: formatter).keyboardType(.numberPad)
            Text("Echo: \(number)")
            Text("Observable")
            TextField("int", value: $model.number, formatter: formatter).keyboardType(.numberPad)
            Text("Echo: \(model.number)")
        }
    }
}

struct TextFieldFormatter_Previews: PreviewProvider {
    static var previews: some View {
        TextFieldFormatter()
    }
}

extension View {
    func endEditing() {
        UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to:nil, from:nil, for:nil)
    }
}
like image 557
Erik Peterson Avatar asked Jan 25 '23 08:01

Erik Peterson


1 Answers

You can create a custom Binding to pass in to the TextField.

This is how to construct a Binding<String> (what the TextField needs):

var string = ""

let myBinding = Binding(get: { string }) {
    string = $0
}

The first argument of the Binding constructor is the getter. This closure should return a value of the desired type.

The second argument is a setter. It is a closure that takes in the new value, and does something with it (usually set another value).

So, if you want to create a Binding<String> to pass in to the TextField that gets/sets appModel.intValue, you can create a Binding<String> and pass it into the TextField like this:

TextField("", text: .init(
    get: { self.appModel.intValue.map(String.init) ?? "" },
    set: { self.appModel.intValue = Int($0) }
))
.keyboardType(.numberPad)
like image 62
Ken Mueller Avatar answered Feb 08 '23 18:02

Ken Mueller