Hello!,
I noticed that the Binding
is not working as expected when we use UITextfield as subViews of a SwiftUI List with the help of UIViewRepresentable protocol.
i have implemented like a dynamic form using List View and the Custom TextField Views, when i was trying to update the value of one field from the list other than first, previously updated elements data getting removed when tapping on done button of the keyboard toolbar.
I have added the code snippets below related to Custom TextField, List Row and for ListView body
struct TextFieldView: UIViewRepresentable {
// MARK: - Internal Properties
private let inputTextField = UITextField()
public var placeholder: String
public var keyboardType: UIKeyboardType
private let actionsHandler = InputAccessoryViewActionsHandler()
@Binding public var value: String?
func makeUIView(context: Context) -> UITextField {
inputTextField.placeholder = placeholder
inputTextField.textColor = AppTheme.primaryText.color
inputTextField.font = Font.Roboto.regular.of(size: 16)
inputTextField.keyboardType = keyboardType
inputTextField.delegate = context.coordinator
inputTextField.accessibilityIdentifier = "specValueTextField"
inputTextField.isAccessibilityElement = true
if keyboardType == .numberPad {
configureAccessoryView()
}
return inputTextField
}
func updateUIView(_ uiView: UITextField, context: Context) {
if let value = self.value {
uiView.text = value
}
}
func makeCoordinator() -> Coordinator {
Coordinator($value)
}
func configureAccessoryView() {
// Accessory View
let toolbar = UIToolbar()
toolbar.sizeToFit()
let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace,
target: nil,
action: nil)
let doneButton = UIBarButtonItem(title: "Done".localized,
style: .plain,
target: self.actionsHandler,
action: #selector(self.actionsHandler.doneButtonAction))
doneButton.tintColor = AppTheme.primary.color
doneButton.accessibilityIdentifier = "Done"
toolbar.setItems([flexibleSpace, doneButton], animated: true)
self.actionsHandler.doneButtonTapped = {
self.value = self.inputTextField.text
self.inputTextField.resignFirstResponder()
}
self.inputTextField.inputAccessoryView = toolbar
}
class InputAccessoryViewActionsHandler {
public var doneButtonTapped: (() -> Void)?
@objc func doneButtonAction() {
self.doneButtonTapped?()
}
}
class Coordinator: NSObject, UITextFieldDelegate {
var text: Binding<String?>
init(_ text: Binding<String?>) {
self.text = text
}
func textField(_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
if let currentValue = textField.text as NSString? {
let proposedValue = currentValue.replacingCharacters(in: range, with: string)
self.text.wrappedValue = proposedValue
}
return true
}
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
textField.resignFirstResponder()
return true
}
}
}
List Row Element view
struct SpecificationRow: View {
@Binding var specification: ClassificationSpecItem
// MARK: - Body
var body: some View {
VStack(alignment: .leading) {
TextFieldView(placeholder: "Enter value",
keyboardType: keyboardType,
value: $specification.specValue)
}.padding(EdgeInsets(top: 12, leading: 0, bottom: 5, trailing: 0))
}
}
and the list body is like below
var body: some View {
VStack {
List($viewModel.specifications) { $spec in
SpecificationRow(specification: $spec)
}
}
in above the viewModel was confirmed to observable and the specifications property was a @Published
property
Am I missing something? Or is this just a bug in SwiftUI? Any help would be greatly appreciated! Thanks!