这就是我在 Swift 3 中的做法(覆盖属性的规则与 Objective-C 不同)。
我想要一个显示日期的按钮,并在点击按钮时从屏幕底部显示日期选择器,以允许用户编辑显示的日期。使用锚定在目标按钮上的弹出框相对容易做到这一点,但我的应用程序仅适用于 iPhone(不是 iPad),因此 UITextField-inputView 模式似乎更好(在 AppStore 应用程序上使用可能会违反 Human界面指南)。
按钮子类:
class EditableButton: UIButton {
// This holds the assigned input view (superclass property is readonly)
private var customInputView: UIView?
// We override the superclass property as computed, and actually
// store into customInputView:
override var inputView: UIView? {
get {
return customInputView
}
set (newValue) {
customInputView = newValue
}
}
// Must be able to become first responder in order to
// get input view to be displayed:
override var canBecomeFirstResponder: Bool {
return true
}
// Setup becoming first responder on tap:
required init?(coder aDecoder: NSCoder) {
super.init(coder: aDecoder)
self.addTarget(self, action: #selector(EditableButton.tap(_:)), for: .touchDown)
// (Not sure if this creates a retain cycle; must investigate)
}
@IBAction func tap(_ sender: UIButton) {
self.becomeFirstResponder()
}
}
查看控制器代码:
class ViewController: UIViewController {
// Set the appropriate subclass in the storyboard too!
@IBOutlet weak var todayButton: EditableButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let datePicker = UIDatePicker()
datePicker.datePickerMode = .date
todayButton.inputView = datePicker
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
todayButton.setTitle(dateFormatter.string(from: Date()), for: .normal)
// Also hook an a target/action to the date picker's
// valueChanged event, to get notified of the user's input
datePicker.addTarget(self, action: #selector(ViewController.changeDate(_:)), for: .valueChanged)
}
@IBAction func changeDate(_ sender: UIDatePicker) {
// (update button title with formatted date)
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
todayButton.setTitle(dateFormatter.string(from: sender.date), for: .normal)
// Dismiss picker:
todayButton.resignFirstResponder()
}
}
注意:
为简单起见,此代码将在指定日期后立即关闭日期选择器 - 即,每当您滚动任何轮子时。这可能不是您想要的:通常,用户一次指定一个日期(年、月等)。此实现不会让用户在关闭日期选择器之前完成指定所有日期组件。
理想情况下,您应该添加一个UIToolbar 作为输入附件视图,并带有一个“完成”按钮来关闭输入视图(即,使按钮退出第一响应者)。这需要进一步修改 UIButton 子类(也可以容纳 inputAccessoryView),但应该是可行的。
编辑:我可以确认输入附件视图也可以以与输入视图完全相同的方式附加到自定义 UIButton 子类:
private var customInputAccessoryView: UIView?
override var inputAccessoryView: UIView? {
get {
return customInputAccessoryView
}
set (newValue) {
customInputAccessoryView = newValue
}
}
现在,我是这样设置的:
let datePicker = UIDatePicker()
datePicker.datePickerMode = .date
todayButton.inputView = datePicker
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
todayButton.setTitle(dateFormatter.string(from: Date()), for: .normal)
datePicker.addTarget(self, action: #selector(ViewController.changeDate(_:)), for: .valueChanged)
let dateToolbar = UIToolbar(frame: CGRect.zero)
dateToolbar.barStyle = .default
let dateDoneButton = UIBarButtonItem(
barButtonSystemItem: .done,
target: self,
action: #selector(ViewController.dateDone(_:)))
let dateSpacer = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
dateToolbar.items = [dateSpacer, dateDoneButton]
dateToolbar.sizeToFit()
todayButton.inputAccessoryView = dateToolbar
并以两个单独的操作进行响应:
@IBAction func changeDate(_ sender: UIDatePicker) {
// Update button title in real time:
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "yyyy-MM-dd"
todayButton.setTitle(dateFormatter.string(from: sender.date), for: .normal)
}
@IBAction func dateDone(_ sender: UIBarButtonItem) {
// ...but dismiss picker only on 'Done':
todayButton.resignFirstResponder()
}
另外,当视图控制器从导航中弹出时,我的自定义按钮子类的deinit() 方法确实会被调用,因此UIButton 不再(?)保留其目标,或者Swift 运行时比我想象的要聪明...
更新:我整理了一个Xcode project demonstrating this custom button(Github 存储库)。修改它以使用自定义UIPickerView(而不是UIDatePicker)应该非常简单。 不是立即显而易见的(至少对我而言)是如何使它与键盘一起作为输入视图工作。