Reputation: 11108
I'm working on an app that allows users to type odds for a bet using 2 separate text fields. So for example, let's say the odds the user wants is 3/1. The user will need to enter 3 into one field, and 1 into the other.
I have 2 state variables:
This is how I use them with the CustomTextField:
CustomTextfield(text: $leftOddsVal, placeHolder: "Enter text", keyboardType: .numberPad)
CustomTextfield(text: $rightOddsVal, placeHolder: "Enter text", keyboardType: .numberPad)
CustomTextfield(text: $state, placeHolder: "Enter text", keyboardType: .numberPad)
This is the code for the custom textfield:
struct CustomTextfield: UIViewRepresentable {
@Binding var text : String
var placeHolder: String
var keyboardType: UIKeyboardType
final class Coordinator: NSObject {
var textField: CustomTextfield
init(_ textField: CustomTextfield) {
self.textField = textField
}
}
func makeCoordinator() -> DabbleTextfield.Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> UITextField {
let textField = UITextField()
textField.backgroundColor = .white
textField.placeholder = self.placeHolder
textField.keyboardType = self.keyboardType
return textField
}
func updateUIView(_ uiView: UITextField, context: Context) {
}
}
What I am trying to do:
I want to take the values of each field, and write some logic to work out how much could be won and lost then update some text labels on the screen. Rather than have the user submit the form before they can see what they may win or lose, I would like the win/lose labels to be updated automatically after all fields have values.
These are how the labels look:
The problem:
The textFieldDidEndEditing delegate method needs to know what method to trigger. This means, I need to modify the CustomTextField to trigger a method inside my viewModel that will fire. Then I can trigger the win/lose calculation method. However, I'm starting to feel as this is a lot of work for something so simple. It seems messy.
I'm unable to access the text field values directly inside the view. This would be simple if I was using SwiftUI's TextField component. However, I'm using UIKit's UITextField component wrapped in a UIRepresentable struct as you can see in my code above.
What I've tried:
I have a view model, and I tried to pass that into the CustomtextField.
I simply declared my environment variable like this:
struct CustomTextfield: UIViewRepresentable {
@EnvironmentObject var viewModel: EnvironmentObject<AnyObject>
@Binding var text : String
var placeHolder: String
var keyboardType: UIKeyboardType
I used AnyObject so that I can cast whatever model I pass into the CustomTextField as the correct type. I don't want to ever need to manually modify the CustomTextField to cater to each model I may use it with.
I now use the CustomTextField like this:
CustomTextfield(viewModel: betViewModel as! EnvironmentObject<BetViewModel>, value: $oddsLeftValue, placeHolder: "Enter text", keyboardType: .numberPad)
And I store a copy of the TextField in a variable in my ViewModel:
func makeUIView(context: Context) -> UITextField {
let textField = UITextField()
if let fieldValue = textField.text {
self.viewModel.textField = fieldValue
}
But I get the following error:
Type 'AnyObject' does not conform to protocol 'ObservableObject'
I also tried setting the binded text value to the value of the textField, but this didn't work either.
I'm hoping someone can give me some insight into what I'm doing wrong. I feel like I'm going in circles, and the cause of this comes from trying to reuse the UIRepresentable UIKit component. It seems to be limiting me, which results in workarounds that seem to be an overkill for what I'm trying to achieve.
All I want to do is allow a user to type in their odds and stake and have the win/lose labels updated dynamically without needing to tap any buttons. All while being able to reuse the CustomTextField.
Looking forward to your suggestions. Thanks in advance.
Upvotes: 1
Views: 1724
Reputation: 258443
The below makes your custom text field operable... for updating external bound, hope this was the goal of such long description.
Tested with Xcode 11.4 / iOS 13.4
Small TestCustomTextfield
at the end was used to check updating label on changes in CustomTextfield
.
struct CustomTextfield: UIViewRepresentable {
@Binding var text : String
var placeHolder: String
var keyboardType: UIKeyboardType
final class Coordinator: NSObject, UITextFieldDelegate {
var parent: CustomTextfield
init(_ parent: CustomTextfield) {
self.parent = parent
}
func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool {
parent.text = textField.text ?? ""
return true
}
}
func makeCoordinator() -> Coordinator {
Coordinator(self)
}
func makeUIView(context: Context) -> UITextField {
let textField = UITextField()
textField.backgroundColor = .white
textField.placeholder = self.placeHolder
textField.keyboardType = self.keyboardType
textField.setContentHuggingPriority(.required, for: .vertical)
textField.delegate = context.coordinator
return textField
}
func updateUIView(_ uiField: UITextField, context: Context) {
if let text = textField.text as NSString? {
parent.text = text.replacingCharacters(in: range, with: string)
} else {
parent.text = ""
}
}
}
struct TestCustomTextfield: View {
@State private var value = ""
var body: some View {
VStack {
Text("Label: \(value)")
CustomTextfield(text: $value, placeHolder: "Your value", keyboardType: .numberPad)
}
}
}
Upvotes: 0