skip to Main Content

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 accessory view of textField View.

I have added the code snippets below related to Custom TextField, List Row and for ListView body

TextField View

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)
}
}

the viewModel was confirmed to ObservableObject 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!

2

Answers


  1. You are creating the view object in the wrong place. Instead of:

    private let inputTextField = UITextField()
    

    do:

    private let inputTextField: UITextField?
    
    func makeUIView(context: Context) -> UITextField {
        inputTextField = UITextField()
    

    Or perhaps you don’t need to hang on to the reference?

    Also you can’t create objects like this in SwiftUI:

    private let actionsHandler = InputAccessoryViewActionsHandler()
    

    You could instead use your coordinator object for the action handling.

    SwiftUI View structs should only init value types. If instantiating objects it needs to be wrapped in @StateObject which associates the lifetime with the View appearing/dissapearing or can be a global or a singleton if associating with lifetime of the app.

    Also in updateUIView you need to update the text field properties from your properties.

    Login or Signup to reply.
  2. You are updating your textfield value in updateUIView method instead of makeUIView of TextFieldView class.

    Just replace your code with the below one

    func makeUIView(context: Context) -> UITextField {
    
     //Assign your value to UITextField here
     inputTextField.text = self.value
    
     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) {
    
       //Comment below code
     
       /* if let value = self.value {
          uiView.text = value
       }*/
    
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search