You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的方法签名规则:

  1. 如果给方法加参数,Selector的写法必须对应(比如@objc func doneClick(_ textField: UITextField)的Selector是#selector(doneClick(_:)))
  2. UIBarButtonItem的action默认只会把按钮本身作为参数传递,没法直接把TextField传进去,这就导致你定义的带TextField参数的方法无法被正确调用,最终触发编译错误。

内容的提问来源于stack exchange,提问作者Renan Aguiar

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.19 09:28:35