使用自定义 UITextField(通过使用 UIViewRepresentable)作为子视图时列表视图的奇怪行为
我注意到,当我们在 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
您在错误的位置创建视图对象。而不是:
do:
或者也许您不需要保留参考文献?
此外,您无法在 SwiftUI 中创建这样的对象:
您可以使用协调器对象来进行操作处理。
SwiftUI View 结构应该只初始化值类型。如果实例化对象,则需要将其包装在
@StateObject
中,它将生命周期与View
的出现/消失相关联,或者如果与应用程序的生命周期相关联,则可以是全局或单例。另外,在
updateUIView
中,您需要从属性中更新文本字段属性。You are creating the view object in the wrong place. Instead of:
do:
Or perhaps you don't need to hang on to the reference?
Also you can't create objects like this in SwiftUI:
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 theView
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.您正在
updateUIView
方法而不是TextFieldView
类的makeUIView
中更新文本字段值。只需将您的代码替换为以下代码即可
You are updating your textfield value in
updateUIView
method instead ofmakeUIView
ofTextFieldView
class.Just replace your code with the below one