I need to write generic code to work with some SwiftUI.TextField of this kind:
basically I need whatever magic the TextField
does inside to convert String to T
, TextField
must be doing this inside to convert the input String value to T and assign to a binding. Something kind of like this:
import SwiftUI
// I need something like this that work for any T
func convert<T>(_ s: String, _ b: Binding<T>) {
// this is what I need:
// how to convert s to T? so we can do this:
// b.wrappedValue = s as! T // of course this won't work, will crash
// so for illustration, I'm just going to use `Double`
b.wrappedValue = Double(s) as! T
}
var d: Double = Double.nan
let binding = Binding<Double>(get: { d }, set: { d = $0 })
// to be use like this: need to work for any Binding<T> where T is unconstraint
convert("123.456", binding)
print(d)
How to make convert
work?
this is actually my code, look to the end of this code to see where my problem is:
import SwiftUI
import Introspect
extension View {
func addDecimalAndDoneKeyboardToolbar<T>(_ value: Binding<T>) -> some View {
modifier(AddDecimalAndDoneKeyboardToolbar(value: value))
}
}
/// Use like this to add keyboard toolbar to a TextField:
/// ```
/// @State var value: Double
/// TextField("Enter a number", value: $value, formatter: NumberFormatter())
/// .addDecimalAndDoneKeyboardToolbar($value)
/// .keyboardType(.numberPad)
struct AddDecimalAndDoneKeyboardToolbar<T>: ViewModifier {
@Binding var value: T
func body(content: Content) -> some View {
content
.introspectTextField(customize: addToolbar)
}
// for keeping a strong ref when this created inside `addToolbar(...)`
@State var buttonHandler: ButtonHadler<T>?
// add a decimal point button to input a "."
// add a done button to commit textField input and dismiss keyboard
func addToolbar(to textField: UITextField) {
let toolBar = UIToolbar(
frame: CGRect(
origin: .zero,
size: CGSize(width: textField.frame.size.width, height: 44)
)
)
let flexButton = UIBarButtonItem(
barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace,
target: nil,
action: nil
)
buttonHandler = ButtonHadler(textField, $value)
let decimalButton = UIBarButtonItem(
title: " • ",
style: .plain,
target: buttonHandler, // or self, action is not called :(
action: #selector(buttonHandler?.decimalPointAction(_:)) // <<<<< why is this not called?
)
let doneButton = UIBarButtonItem(
title: "Done",
style: .done,
target: buttonHandler, // or self, action is not called :(
action: #selector(buttonHandler?.doneAction(_:)) // <<<<< why is this not called?
)
toolBar.setItems([decimalButton, flexButton, doneButton], animated: true)
textField.inputAccessoryView = toolBar
}
final class ButtonHadler<T> {
let textField: UITextField
@Binding var field: T
init(_ t: UITextField, _ f: Binding<T>) {
textField = t
_field = f
}
@objc func decimalPointAction(_ button: UIBarButtonItem) -> Void {
textField.text = (textField.text ?? "0") + "."
}
@objc func doneAction(_ button: UIBarButtonItem) -> Void {
// 😳😩👇👇
// problem here: how to convert a string to T? Don't even know what T is
// ideally, I want to to be unconstraint, just like TextField
// what to do here?
field = (textField.text ?? "0") as! T // this will crash at runtime! won't work!!!
textField.resignFirstResponder()
}
}
}
How does the TextField
do the convert from String to T? It might be using the Formatter
, if so, I'm willing to pass the formatter
into my code.