了解 inputView 和 firstResponder:如何创建一个将 UIDatePicker 显示为键盘的 UITableViewCell?
Posted
技术标签:
【中文标题】了解 inputView 和 firstResponder:如何创建一个将 UIDatePicker 显示为键盘的 UITableViewCell?【英文标题】:Understanding inputView and firstResponder: How to create a UITableViewCell which shows a UIDatePicker as Keyboard? 【发布时间】:2020-05-26 21:46:12 【问题描述】:我想创建一个自定义的UITableViewCell
,它显示一些日期并在选择时将UIDatePicker
显示为键盘。
虽然我发现了几个处理这个问题的主题,但它们都提供了相同的解决方案:将 UITextField
添加到单元格并将 UIDatepPicker
设置为此 TextField 的 inputView
。
这个解决方案效果很好,但我不喜欢它。这很 hacky,如果不应该编辑文本,则不需要使用 TextField。我只需要一个显示数据的标签和一个用于更改此日期的 DatePicker 键盘。
我尝试了什么:
再深入一点,我发现UITableViewCell
有自己的inputView
属性,它继承自UIResponder
。但是,我无法覆盖 (Cannot override with a stored property 'inputView'
) 或分配此属性 (Cannot assign to property: 'inputView' is a get-only property
)。 canBecomeFirstResponder
和其他必须实现/更改才能让单元格在 firstResponder / inputView 工作的属性也是如此。
我想知道UITextField
是如何实现的,因为这也是UIResponder
的子类。
长话短说:
是否可以创建我自己的UIView
(甚至更好的UITableViewCell
)子类,作为一种输入视图并显示自定义键盘?
或者使用(隐藏的)TextField 真的是最好的解决方案?
【问题讨论】:
快速搜索,这里只是一个可能满足您需求的示例:***.com/a/34703276/6257435 感谢@DonMag,但您链接的答案解释了如何在 TableViewCell 内显示 UIDatePicker 并且与问题无关(如何在不使用隐藏 TextField 的情况下显示单元格的键盘/输入视图)。 .. 对不起,从您的描述中认为这是一个选项...澄清一下...您想点击一个单元格(或单元格中的一个对象)并显示一个日期选择器,就好像它是键盘吗?或者,您想显示一个顶部带有日期选择器的键盘? 尽管你所说的方式看起来“hacky”,但这是标准做法。我知道您不想要UITextField
,只想在您按下单元格时出现日期键盘,但我会通过在您的单元格中添加UITextField
并将其样式设置为UILabel
来解决此问题。然后,当您点击它或单元格时,它将编辑看起来像标签的字段,您的键盘设置为日期选择器
【参考方案1】:
正如评论中已经提到的那样,它没有任何“hacky”。这是一个标准程序。尽管您的输入视图可能看起来像一个标签或按钮,但它仍然是一个输入字段,它打开一个类似于使用日期选择器的键盘的对话框。
但如果你真的不喜欢使用这个,那么有几种选择。我假设当按下一个单元格(或其中的一部分)时,日期选择器应该出现在某处。那么答案很简单。创建一个将在按下该区域时触发的偶数。您可以使用按钮,可以使用表格视图单元格委托来检查何时按下行,或者如果您认为它更适合您,您甚至可以添加手势识别器。
无论如何,一旦您举办了活动,您就可以在任何您想要的地方展示您的日期选择器。这可以是显示一个新窗口或将其放入您的视图控制器中,如果您愿意,甚至可以放入您的表格视图中。
但是,无论您自定义什么,都会否定 ios 设备的本机逻辑,这可能会打扰一些用户。例如,如果您在选择器显示时没有制作完全相同的动画,那么对于习惯于以原生方式处理事物的用户来说,它可能看起来很奇怪。
或者也许你可以做得比原生更好,在这种情况下就去吧。但请注意,您可能需要完成相当多的任务。动画、触摸事件、关闭日期选择器、重新定位您的表格视图以使日期选择器不会与您的单元格重叠......
【讨论】:
【参考方案2】:你可以给一个单元格自己的inputView
——你必须override
它,它只需要几个步骤来实现。
这里是一个简单的例子(很快就放在一起,所以不要考虑这个“生产就绪”的代码):
点击一行将导致单元格为becomeFirstResponder
。它的inputView
是一个自定义视图,其中UIDatePicker
作为子视图。
当您在选择器中选择新的日期/时间时,它将更新该单元格(以及支持数据源)。
点击当前单元格将使其变为resignFirstResponder
,这将关闭DatePickerKeyboard
。
这里是代码。该单元格是基于代码的(不是 Storyboard Prototype),不使用 IBOutlet
s 或 IBAction
s...只需添加一个 UITableViewController
并将其分配给 InputViewTableViewController
:
// protocol so we can send back the newly selected date
@objc protocol MyDatePickerProtocol
@objc func updateDate(_ newDate: Date)
// basic UIView with UIDatePicker added as subview
class DatePickKeyboard: UIView
var theDatePicker: UIDatePicker = UIDatePicker()
weak var delegate: MyDatePickerProtocol?
init(delegate: MyDatePickerProtocol)
self.delegate = delegate
super.init(frame: .zero)
configure()
required init?(coder: NSCoder)
fatalError("init(coder:) has not been implemented")
// UIDatePicker target
extension DatePickKeyboard
@objc func dpChanged(_ sender: Any?) -> Void
if let dp = sender as? UIDatePicker
// tell the delegat we have a new date
delegate?.updateDate(dp.date)
// MARK: - Private initial configuration methods
private extension DatePickKeyboard
func configure()
autoresizingMask = [.flexibleWidth, .flexibleHeight]
theDatePicker.addTarget(self, action: #selector(dpChanged(_:)), for: .valueChanged)
addSubview(theDatePicker)
theDatePicker.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
theDatePicker.centerXAnchor.constraint(equalTo: centerXAnchor),
theDatePicker.centerYAnchor.constraint(equalTo: centerYAnchor),
theDatePicker.widthAnchor.constraint(equalTo: widthAnchor, multiplier: 1.0),
theDatePicker.heightAnchor.constraint(equalTo: heightAnchor, multiplier: 1.0),
])
// table view cell with a single UILabel
// enable canBecomeFirstResponder and use
// DatePickerKeyboard view instead of default keyboard
class InputViewCell: UITableViewCell, MyDatePickerProtocol
var theLabel: UILabel =
let v = UILabel()
return v
()
var myInputView: UIView?
var myCallback: ((Date)->())?
var myDate: Date = Date()
var theDate: Date
get
return self.myDate
set
self.myDate = newValue
let df = DateFormatter()
df.dateFormat = "MMM d, h:mm a"
let s = df.string(from: self.myDate)
theLabel.text = s
override var canBecomeFirstResponder: Bool return true
override var inputView: UIView
get
return self.myInputView!
set
self.myInputView = newValue
@discardableResult
override func becomeFirstResponder() -> Bool
let becameFirstResponder = super.becomeFirstResponder()
if let dpv = self.inputView as? DatePickKeyboard
dpv.theDatePicker.date = self.myDate
updateUI()
return becameFirstResponder
@discardableResult
override func resignFirstResponder() -> Bool
let resignedFirstResponder = super.resignFirstResponder()
updateUI()
return resignedFirstResponder
func updateUI() -> Void
// change the appearance if desired
backgroundColor = isFirstResponder ? .yellow : .clear
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?)
super.init(style: style, reuseIdentifier: reuseIdentifier)
commonInit()
required init?(coder: NSCoder)
super.init(coder: coder)
commonInit()
func commonInit() -> Void
theLabel.translatesAutoresizingMaskIntoConstraints = false
contentView.addSubview(theLabel)
let v = contentView.layoutMarginsGuide
NSLayoutConstraint.activate([
theLabel.topAnchor.constraint(equalTo: v.topAnchor),
theLabel.bottomAnchor.constraint(equalTo: v.bottomAnchor),
theLabel.leadingAnchor.constraint(equalTo: v.leadingAnchor),
theLabel.trailingAnchor.constraint(equalTo: v.trailingAnchor),
])
inputView = DatePickKeyboard(delegate: self)
@objc func updateDate(_ newDate: Date) -> Void
self.theDate = newDate
myCallback?(newDate)
// must conform to UIKeyInput, even if we're not using the standard funcs
extension InputViewCell: UIKeyInput
var hasText: Bool return false
func insertText(_ text: String)
func deleteBackward()
// simple table view controller
class InputViewTableViewController: UITableViewController
var theData: [Date] = [Date]()
override func viewDidLoad()
super.viewDidLoad()
// generate some date data to work with - 25 dates incrementing by 2 days
var d = Calendar.current.date(byAdding: .day, value: -50, to: Date())
for _ in 1...25
theData.append(d!)
d = Calendar.current.date(byAdding: .day, value: 2, to: d!)
// register our custom cell
tableView.register(InputViewCell.self, forCellReuseIdentifier: "InputViewCell")
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
return theData.count
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
let c = tableView.dequeueReusableCell(withIdentifier: "InputViewCell", for: indexPath) as! InputViewCell
c.theDate = theData[indexPath.row]
c.myCallback = d in
self.theData[indexPath.row] = d
c.selectionStyle = .none
return c
// on row tap, either become or resign as first responder
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
if let c = tableView.cellForRow(at: indexPath) as? InputViewCell
if c.isFirstResponder
c.resignFirstResponder()
else
c.becomeFirstResponder()
tableView.deselectRow(at: indexPath, animated: false)
【讨论】:
以上是关于了解 inputView 和 firstResponder:如何创建一个将 UIDatePicker 显示为键盘的 UITableViewCell?的主要内容,如果未能解决你的问题,请参考以下文章
iOS:自定义 UITextField 的 inputView 大小
UIPickerView 作为多个 UITextField 的 inputView
带有搜索栏的 UITableView 作为 inputView
从 Textfield.inputview 加载 xib UIView