簡體   English   中英

了解 inputView 和 firstResponder:如何創建將 UIDatePicker 顯示為鍵盤的 UITableViewCell?

[英]Understanding inputView and firstResponder: How to create a UITableViewCell which shows a UIDatePicker as Keyboard?

我想創建一個自定義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 真的是最好的解決方案嗎?

正如評論中已經提到的那樣,它沒有什么“hacky”。 這是一個標准程序。 盡管您的輸入視圖可能看起來像一個標簽或按鈕,但它仍然是一個輸入字段,它打開一個類似於使用日期選擇器的鍵盤的對話框。

但是如果你真的不想使用這個,那么有幾種選擇。 我假設當一個單元格(或它的一部分)被按下時,日期選擇器應該出現在某處。 那么答案很簡單; 創建一個偶數,當該區域被按下時會觸發。 您可以使用按鈕,也可以使用表格視圖單元格委托來檢查何時按下行,或者如果您認為它更適合您,甚至可以添加手勢識別器。

在任何情況下,一旦您有活動,您就可以在任何您想要的地方展示您的日期選擇器。 如果您願意,這可以是呈現一個新窗口或將其放入您的視圖控制器或什至您的表格視圖中。

但是無論您自定義什么,都會否定 iOS 設備的本機邏輯,這可能會打擾一些用戶。 例如,如果您在選擇器顯示時沒有制作完全相同的動畫,對於習慣於以本機方式擁有事物的用戶來說,它可能看起來很奇怪。

或者也許你可以做得比本地好得多,在這種情況下就去做吧。 但請注意,您可能有相當多的任務要完成。 動畫、觸摸事件、關閉日期選擇器、重新定位您的表格視圖以便日期選擇器不會與您的單元格重疊...

可以給一個單元格自己的inputView —— 你必須override它,它只需要幾個步驟來實現。

這是一個簡單的例子(很快就組合在一起,所以不要考慮這個“生產就緒”代碼):

在此處輸入圖片說明

點擊一行將導致單元格成為becomeFirstResponder 它的inputView是一個自定義視圖,帶有一個UIDatePicker作為子視圖。

當您在選擇器中選擇新的日期/時間時,它將更新該單元格(以及支持數據源)。

點擊當前單元格將導致它resignFirstResponder這將關閉DatePickerKeyboard

這是代碼。 該單元格是基於代碼的(不是 Storyboard Prototype),並且不使用IBOutletIBAction ......只需添加一個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)
    }

}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM