【问题标题】:Understanding inputView and firstResponder: How to create a UITableViewCell which shows a UIDatePicker as Keyboard?了解 inputView 和 firstResponder:如何创建一个将 UIDatePicker 显示为键盘的 UITableViewCell?
【发布时间】: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 真的是最好的解决方案?

【问题讨论】:

  • 快速搜索,这里只是一个可能满足您需求的示例:stackoverflow.com/a/34703276/6257435
  • 感谢@DonMag,但您链接的答案解释了如何在 TableViewCell 内显示 UIDatePicker 并且与问题无关(如何在不使用隐藏 TextField 的情况下显示单元格的键盘/输入视图)。 ..
  • 对不起,从您的描述中认为这是一个选项...澄清一下...您想点击一个单元格(或单元格中的一个对象)并显示一个日期选择器,就好像它是键盘吗?或者,您想显示一个顶部带有日期选择器的键盘?
  • 尽管你所说的方式看起来“hacky”,但这是标准做法。我知道您不想要UITextField,只想在您按下单元格时出现日期键盘,但我会通过在您的单元格中添加UITextField 并将其样式设置为UILabel 来解决此问题。然后,当您点击它或单元格时,它将编辑看起来像标签的字段,您的键盘设置为日期选择器

标签: ios swift uitableview first-responder inputview


【解决方案1】:

正如评论中已经提到的那样,它没有任何“hacky”。这是一个标准程序。尽管您的输入视图可能看起来像一个标签或按钮,但它仍然是一个输入字段,它打开一个类似于使用日期选择器的键盘的对话框。

但如果你真的不喜欢使用这个,那么有几种选择。我假设当按下一个单元格(或其中的一部分)时,日期选择器应该出现在某处。那么答案很简单。创建一个将在按下该区域时触发的偶数。您可以使用按钮,可以使用表格视图单元格委托来检查何时按下行,或者如果您认为它更适合您,您甚至可以添加手势识别器。

无论如何,一旦您举办了活动,您就可以在任何您想要的地方展示您的日期选择器。这可以是显示一个新窗口或将其放入您的视图控制器中,如果您愿意,甚至可以放入您的表格视图中。

但是,无论您自定义什么,都会否定 iOS 设备的本机逻辑,这可能会打扰一些用户。例如,如果您在选择器显示时没有制作完全相同的动画,那么对于习惯于以原生方式处理事物的用户来说,它可能看起来很奇怪。

或者也许你可以做得比原生更好,在这种情况下就去吧。但请注意,您可能需要完成相当多的任务。动画、触摸事件、关闭日期选择器、重新定位您的表格视图以使日期选择器不会与您的单元格重叠......

【讨论】:

    【解决方案2】:

    可以给一个单元格自己的inputView——你必须override它,它只需要几个步骤来实现。

    这里是一个简单的例子(很快就放在一起,所以不要考虑这个“生产就绪”的代码):

    点击一行将导致单元格为becomeFirstResponder。它的inputView 是一个自定义视图,其中UIDatePicker 作为子视图。

    当您在选择器中选择新的日期/时间时,它将更新该单元格(以及支持数据源)。

    点击当前单元格将使其变为resignFirstResponder,这将关闭DatePickerKeyboard

    这里是代码。该单元格是基于代码的(不是 Storyboard Prototype),不使用 IBOutlets 或 IBActions...只需添加一个 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)
        }
    
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-06-29
      • 2012-11-19
      相关资源
      最近更新 更多