使用自定义 UITextField(通过使用 UIViewRepresentable)作为子视图时列表视图的奇怪行为

发布于 2025-01-10 12:28:29 字数 3686 浏览 1 评论 0原文

我注意到,当我们在 UIViewRepresentable 协议的帮助下使用 UITextfield 作为 SwiftUI 列表的子视图时,绑定无法按预期工作。

我已经使用列表视图和自定义文本字段视图实现了类似动态表单的功能,当我尝试更新列表中除第一个字段之外的一个字段的值时,当点击附件视图的完成按钮时,先前更新的元素数据将被删除文本字段视图。

相关的代码片段

我添加了下面与自定义文本字段、列表行和 ListView 主体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))
 }
}

,列表主体如下所示

  var body: some View {
  VStack {
   List($viewModel.specifications) { $spec in
       SpecificationRow(specification: $spec)
}
}

viewModel 已确认为 ObservableObject,并且规范属性是 @Published 属性

我遗漏了什么吗?或者这只是 SwiftUI 中的一个错误?任何帮助将不胜感激!谢谢!

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!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

吃颗糖壮壮胆 2025-01-17 12:28:29

您在错误的位置创建视图对象。而不是:

private let inputTextField = UITextField()

do:

private let inputTextField: UITextField?

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

或者也许您不需要保留参考文献?

此外,您无法在 SwiftUI 中创建这样的对象:

private let actionsHandler = InputAccessoryViewActionsHandler()

您可以使用协调器对象来进行操作处理。

SwiftUI View 结构应该只初始化值类型。如果实例化对象,则需要将其包装在 @StateObject 中,它将生命周期与 View 的出现/消失相关联,或者如果与应用程序的生命周期相关联,则可以是全局或单例。

另外,在 updateUIView 中,您需要从属性中更新文本字段属性。

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.

秋叶绚丽 2025-01-17 12:28:29

您正在 updateUIView 方法而不是 TextFieldView 类的 makeUIView 中更新文本字段值。

只需将您的代码替换为以下代码即可

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
   }*/

}

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
   }*/

}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文