Swift中#selector传参问题:如何用同一函数处理两个TextField的DatePicker?
解决iOS中复用DatePicker时@objc方法带参数的编译错误问题
嘿,我刚好碰到过类似的问题!你遇到的编译错误本质是**@objc标记的方法要兼容Objective-C运行时规则**,直接给doneClick加参数的话,一方面Selector的写法容易出错,另一方面UIBarButtonItem的action默认只会把按钮本身作为参数传递,没法直接把TextField传进去。下面给你两种实用的复用方案,既能避免代码重复,又能解决这个错误:
方案一:利用Tag+关联对象区分输入框
这种方式适合逻辑比较简单的场景,通过给TextField设置唯一Tag来区分不同输入框,再用关联对象把DatePicker和对应的TextField绑定:
class InvoiceViewController: UIViewController { @IBOutlet weak var invoiceDateTextField: UITextField! @IBOutlet weak var dueDateTextField: UITextField! override func viewDidLoad() { super.viewDidLoad() // 给两个TextField分别配置DatePicker,设置不同Tag setupDatePicker(for: invoiceDateTextField, tag: 1) setupDatePicker(for: dueDateTextField, tag: 2) } // 复用的DatePicker配置函数 private func setupDatePicker(for textField: UITextField, tag: Int) { let datePicker = UIDatePicker() datePicker.datePickerMode = .date datePicker.preferredDatePickerStyle = .wheels textField.inputView = datePicker // 配置完成工具栏 let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: 44)) let doneBtn = UIBarButtonItem(title: "完成", style: .done, target: self, action: #selector(doneClick)) let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) toolbar.items = [flexibleSpace, doneBtn] textField.inputAccessoryView = toolbar // 设置Tag区分输入框 textField.tag = tag // 用关联对象把DatePicker和TextField绑定 objc_setAssociatedObject(textField, &AssociatedKeys.datePicker, datePicker, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } // 无参数的@objc处理函数 @objc private func doneClick() { // 获取当前正在编辑的TextField guard let currentTextField = UIResponder.currentFirst() as? UITextField else { return } // 取出绑定的DatePicker guard let datePicker = objc_getAssociatedObject(currentTextField, &AssociatedKeys.datePicker) as? UIDatePicker else { return } // 格式化日期 let dateFormatter = DateFormatter() dateFormatter.dateStyle = .medium let dateStr = dateFormatter.string(from: datePicker.date) // 根据Tag区分处理不同输入框 switch currentTextField.tag { case 1: invoiceDateTextField.text = dateStr // 这里可以添加开票日期的专属逻辑 case 2: dueDateTextField.text = dateStr // 这里可以添加到期日期的专属逻辑 default: break } // 收起选择器 currentTextField.resignFirstResponder() } } // 扩展UIResponder获取当前第一响应者 extension UIResponder { static func currentFirst() -> UIResponder? { _currentFirstResponder = nil UIApplication.shared.sendAction(#selector(findFirstResponder(_:)), to: nil, from: nil, for: nil) return _currentFirstResponder } private static weak var _currentFirstResponder: UIResponder? @objc private func findFirstResponder(_ sender: Any) { UIResponder._currentFirstResponder = self } } // 关联对象的Key定义 private struct AssociatedKeys { static var datePicker = "datePicker" }
方案二:用闭包实现更灵活的回调
这种方式不需要依赖Tag,每个TextField的处理逻辑直接通过闭包定义,代码可读性更高:
class InvoiceViewController: UIViewController { @IBOutlet weak var invoiceDateTextField: UITextField! @IBOutlet weak var dueDateTextField: UITextField! override func viewDidLoad() { super.viewDidLoad() // 配置开票日期输入框 setupDatePicker(for: invoiceDateTextField) { [weak self] selectedDate in let formatter = DateFormatter() formatter.dateStyle = .medium self?.invoiceDateTextField.text = formatter.string(from: selectedDate) // 开票日期专属逻辑写在这里 } // 配置到期日期输入框 setupDatePicker(for: dueDateTextField) { [weak self] selectedDate in let formatter = DateFormatter() formatter.dateStyle = .medium self?.dueDateTextField.text = formatter.string(from: selectedDate) // 到期日期专属逻辑写在这里 } } private func setupDatePicker(for textField: UITextField, completion: @escaping (Date) -> Void) { let datePicker = UIDatePicker() datePicker.datePickerMode = .date textField.inputView = datePicker // 配置工具栏 let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: view.frame.width, height: 44)) let doneBtn = UIBarButtonItem(title: "完成", style: .done, target: self, action: #selector(handleDoneTap)) // 把闭包和按钮绑定 objc_setAssociatedObject(doneBtn, &AssociatedKeys.doneAction) { [weak self] in guard let self = self else { return } completion(datePicker.date) textField.resignFirstResponder() } let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) toolbar.items = [flexibleSpace, doneBtn] textField.inputAccessoryView = toolbar } @objc private func handleDoneTap(_ sender: UIBarButtonItem) { // 取出绑定的闭包并执行 if let action = objc_getAssociatedObject(sender, &AssociatedKeys.doneAction) as? () -> Void { action() } } } private struct AssociatedKeys { static var doneAction = "doneAction" }
为什么你原来的写法会报错?
@objc标记的方法必须符合Objective-C的方法签名规则:
- 如果给方法加参数,Selector的写法必须对应(比如
@objc func doneClick(_ textField: UITextField)的Selector是#selector(doneClick(_:))) - UIBarButtonItem的action默认只会把按钮本身作为参数传递,没法直接把TextField传进去,这就导致你定义的带TextField参数的方法无法被正确调用,最终触发编译错误。
内容的提问来源于stack exchange,提问作者Renan Aguiar
相关产品推荐
相关产品推荐

