SwiftUI与UIKit混合开发:List拖动数据UI同步异常及日期错位
问题描述
实现了一个包含可拖动单元格的SwiftUI List,每个单元格包含自定义的PickerTextField(基于UIKit的UIDatePicker封装)。拖动单元格调整顺序后,编辑日期时发现修改的是拖动前对应位置的数据,而非当前选中的单元格数据,导致数组options与List显示顺序不同步,编辑操作错位。
现有代码
MyView 实现
struct MyView: View { @State var options: [DatePollOption] = [DatePollOption(date: ""),DatePollOption(date: "")] var body: some View{ List{ Section{ ForEach($options) { $option in HStack{ PickerTextField( placeholder: "GG", date: $option.date) } } .onMove(perform: move) } } } func move(from source: IndexSet, to destination: Int) { options.move(fromOffsets: source, toOffset: destination) } } struct DatePollOption : Identifiable { var id: String = UUID().uuidString var date : String? }
PickerTextField 实现
class Helper{ public var dateChanged : (()-> Void)? public var doneButtonTapped: (()-> Void)? @objc func dateValueChanged(){ self.dateChanged?() } } struct PickerTextField : UIViewRepresentable{ var textField = UITextField() @State var datePickerView = UIDatePicker() let toolbar = UIToolbar() let helper = Helper() public var placeholder: String @Binding public var date: String? @Binding var dates : [DatePollOption] var dateFormatterDate : DateFormatter = { let dateFormatter = DateFormatter() dateFormatter.dateFormat = "dd MMM yyy" return dateFormatter }() func makeUIView(context: Context) -> UITextField { self.datePickerView.preferredDatePickerStyle = .wheels self.datePickerView.addTarget(self.helper, action: #selector(self.helper.dateValueChanged), for: .valueChanged) self.textField.placeholder = self.placeholder self.textField.inputView = self.datePickerView textField.delegate = context.coordinator self.textField.inputAccessoryView = toolbar self.helper.dateChanged = { date = dateFormatterDate.string(from: self.datePickerView.date) } return self.textField } func makeCoordinator() -> Coordinator { return Coordinator(datePicker: $datePickerView, date: $date, textField: textField, toolBar: toolbar) } func updateUIView(_ uiView: UITextField, context: Context) { if let selectedDate = self.date { uiView.text = selectedDate } } class Coordinator: NSObject,UITextFieldDelegate,UIToolbarDelegate{ @Binding var datePicker : UIDatePicker var textField : UITextField @Binding public var date: String? let toolBar : UIToolbar var dateFormatter : DateFormatter = { let dateFormatter = DateFormatter() dateFormatter.dateFormat = "HH:mm" return dateFormatter }() let helper = Helper() init(datePicker: Binding<UIDatePicker>,date: Binding<String?>,textField: UITextField,toolBar: UIToolbar) { self._datePicker = datePicker self._date = date self.textField = textField self.toolBar = toolBar super.init() let doneButton = UIBarButtonItem(title: "Done", style: .done, target: self, action: #selector(doneButtonaction)) toolBar.setItems([doneButton], animated: false) } @objc func doneButtonaction(){ if self.date == nil { self.date = dateFormatter.string(from: self.datePicker.date) self.textField.resignFirstResponder() } self.textField.resignFirstResponder() } } }
问题原因分析
核心问题出在PickerTextField的实现违反了UIViewRepresentable的设计规范:
- 结构体存储UIKit视图实例:将
textField、datePickerView直接作为结构体属性,而非在makeUIView内创建,SwiftUI复用结构体实例时会导致旧视图与新绑定关联错位。 - 用@State存储UIKit视图:
datePickerView使用@State修饰,该状态属于视图内部状态,单元格拖动复用后无法与新绑定同步,导致编辑操作指向旧数据。 - 冗余绑定参数:
dates绑定未被使用,增加代码复杂度。 - 日期格式不一致:
dateChanged与doneButtonaction使用不同的日期格式,可能导致显示与编辑逻辑冲突。
解决方案
修改PickerTextField实现,遵循UIViewRepresentable的正确用法,确保视图与绑定的同步性:
修正后的PickerTextField代码
class Helper{ public var dateChanged : ((Date)-> Void)? public var doneButtonTapped: (()-> Void)? @objc func dateValueChanged(_ sender: UIDatePicker){ self.dateChanged?(sender.date) } } struct PickerTextField : UIViewRepresentable{ public var placeholder: String @Binding public var date: String? private let dateFormatter: DateFormatter = { let formatter = DateFormatter() formatter.dateFormat = "dd MMM yyyy" return formatter }() private let timeFormatter: DateFormatter = { let formatter = DateFormatter() formatter.dateFormat = "HH:mm" return formatter }() func makeUIView(context: Context) -> UITextField { // 在makeUIView内创建所有UIKit视图,避免复用问题 let textField = UITextField() textField.placeholder = placeholder let datePickerView = UIDatePicker() datePickerView.preferredDatePickerStyle = .wheels datePickerView.addTarget(context.coordinator.helper, action: #selector(context.coordinator.helper.dateValueChanged(_:)), for: .valueChanged) textField.inputView = datePickerView // 创建工具栏 let toolbar = UIToolbar() toolbar.sizeToFit() let doneButton = UIBarButtonItem(title: "Done", style: .done, target: context.coordinator, action: #selector(context.coordinator.doneButtonaction)) toolbar.setItems([doneButton], animated: false) textField.inputAccessoryView = toolbar textField.delegate = context.coordinator return textField } func makeCoordinator() -> Coordinator { Coordinator(date: $date, dateFormatter: dateFormatter, timeFormatter: timeFormatter) } func updateUIView(_ uiView: UITextField, context: Context) { // 更新TextField内容,同步DatePicker选中日期 uiView.text = date if let dateStr = date, let selectedDate = dateFormatter.date(from: dateStr) ?? timeFormatter.date(from: dateStr) { (uiView.inputView as? UIDatePicker)?.date = selectedDate } } class Coordinator: NSObject, UITextFieldDelegate{ @Binding var date: String? private let dateFormatter: DateFormatter private let timeFormatter: DateFormatter let helper = Helper() init(date: Binding<String?>, dateFormatter: DateFormatter, timeFormatter: DateFormatter) { self._date = date self.dateFormatter = dateFormatter self.timeFormatter = timeFormatter super.init() helper.dateChanged = { [weak self] selectedDate in guard let self = self else { return } self.date = self.dateFormatter.string(from: selectedDate) } } @objc func doneButtonaction(){ guard let datePicker = UIApplication.shared.keyWindow?.rootViewController?.view.subviews.first(where: { $0 is UIDatePicker }) as? UIDatePicker else { return } if date == nil { date = timeFormatter.string(from: datePicker.date) } UIApplication.shared.sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) } // 点击TextField时同步DatePicker日期 func textFieldDidBeginEditing(_ textField: UITextField) { if let dateStr = date, let selectedDate = dateFormatter.date(from: dateStr) ?? timeFormatter.date(from: dateStr) { (textField.inputView as? UIDatePicker)?.date = selectedDate } } } }
关键修改点说明
- 不在结构体存储UIKit视图:所有UIKit视图均在
makeUIView内创建,确保每个PickerTextField对应独立的视图实例,避免复用导致的绑定错位。 - 移除@State修饰的UIKit视图:删除
@State var datePickerView,通过Coordinator处理回调,保证绑定与视图状态同步。 - 优化回调逻辑:Helper的
dateChanged改为接收Date参数,通过Coordinator持有绑定,确保数据更新指向正确的数组元素。 - 同步视图与绑定数据:在
updateUIView和textFieldDidBeginEditing中,根据绑定的日期字符串同步DatePicker的选中状态,保证编辑时显示正确的日期。 - 移除冗余参数:删除未使用的
dates绑定,简化代码结构。 - 统一日期格式处理:将格式器传入Coordinator,避免重复创建,同时兼容两种日期格式的解析。
验证效果
修改后,拖动单元格调整顺序后,编辑任意单元格的日期都会正确修改当前显示位置对应的options数组元素,数据与UI显示顺序完全同步。
内容的提问来源于stack exchange,提问作者Dzondzula
相关产品推荐
相关产品推荐

