Show UIPickerView like a keyboard, without UITextField

后端 未结 6 820
长发绾君心
长发绾君心 2021-02-05 15:56

I\'m looking for a way to present a UIPickerView when the user taps on a UIBarButtonItem. Imagine a filter for the table view results.

I know I

相关标签:
6条回答
  • 2021-02-05 16:38

    The cleanest way at least for me, and for Swift4 + Autolayout solution, without a UITextField is adding a UIView as a container of the UIPickerView and a UIToolbar for the top views/buttons.

    Then toggle the inset/offset of that container view with animation if you want, to hide and unhide the picker.

    PROPERTIES

    private lazy var view_PickerContainer: UIView = {
        let view = UIView()
        view.backgroundColor = .white
        view.addSubview(self.timePicker)
        view.addSubview(self.toolbar_Picker)
        return view
    }()
    
    private lazy var timePicker: UIDatePicker = {
        let picker = UIDatePicker()
        picker.minimumDate = Date()
        picker.datePickerMode = .time
        picker.setDate(Date(), animated: true)
        return picker
    }()
    
    private let timeFormatter: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "hh:mm a"
        return formatter
    }()
    
    private lazy var toolbar_Picker: UIToolbar = {
        let toolbar = UIToolbar()
        toolbar.barStyle = .blackTranslucent
        toolbar.barTintColor = .blueDarkText
        toolbar.tintColor = .white
        self.embedButtons(toolbar)
        return toolbar
    }()
    

    I use SnapKit to layout my views programmatically. You can use the timePicker's bounds.size as reference for your layout.

    IMPLEMENTATION

    In viewDidLoad()

        // Setup timepicker and its container.
        self.view.addSubview(self.view_PickerContainer)
        self.view_PickerContainer.snp.makeConstraints { (make) in
            make.height.equalTo(self.timePicker.bounds.size.height + 50.0)
            make.leading.trailing.equalToSuperview()
            self.constraint_PickerContainerBottom = make.bottom.equalToSuperview().inset(-500.0).constraint
        }
    
        self.timePicker.snp.makeConstraints { (make) in
            make.height.equalTo(self.timePicker.bounds.size.height)
            make.width.equalToSuperview()
            make.bottom.equalToSuperview()
        }
    
        // Add toolbar for buttons.
        self.toolbar_Picker.snp.makeConstraints { (make) in
            make.height.equalTo(40.0)
            make.top.leading.trailing.equalToSuperview()
        }
    

    Embedding top views

    private func embedButtons(_ toolbar: UIToolbar) {
        func setupLabelBarButtonItem() -> UIBarButtonItem {
            let label = UILabel()
            label.text = "Set Alarm Time"
            label.textColor = .white
            return UIBarButtonItem(customView: label)
        }
    
        let todayButton = UIBarButtonItem(title: "Today", style: .plain, target: self, action: #selector(self.todayPressed(_:)))
    
        let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(self.donePressed(_:)))
    
        let flexButton = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: self, action: nil)
    
        toolbar.setItems([todayButton, flexButton, setupLabelBarButtonItem(), flexButton, doneButton], animated: true)
    }
    

    And the result looks like this:

    0 讨论(0)
  • 2021-02-05 16:43

    Based on borisgolovnev's suggestion and CodeMonkey's answer, I have implemented the idea with an invisible UITextField in the following way in Swift 5.

    import UIKit
    
    class PickerViewPresenter: UITextField, UIPickerViewDataSource, UIPickerViewDelegate {
        private lazy var doneToolbar: UIToolbar = {
            let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 50))
    
            let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
            let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneButtonTapped))
    
            let items = [flexSpace, doneButton]
            toolbar.items = items
            toolbar.sizeToFit()
    
            return toolbar
        }()
    
        private lazy var pickerView: UIPickerView = {
            let pickerView = UIPickerView()
            pickerView.dataSource = self
            pickerView.delegate = self
            return pickerView
        }()
    
        var items: [Item] = []
        var didSelectItem: ((Item) -> Void)?
    
        private var selectedItem: Item?
    
        init() {
            super.init(frame: .zero)
            setupView()
        }
    
        required init?(coder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
        private func setupView() {
            inputView = pickerView
            inputAccessoryView = doneToolbar
        }
    
        @objc private func doneButtonTapped() {
            if let selectedItem = selectedItem {
                didSelectItem?(selectedItem)
            }
            resignFirstResponder()
        }
    
        func showPicker() {
            becomeFirstResponder()
        }
    
        func numberOfComponents(in pickerView: UIPickerView) -> Int {
            return 1
        }
    
        func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
            return items.count
        }
    
        func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
            return items[row].name
        }
    
        func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
            selectedItem = items[row]
        }
    }
    

    Usage in your ViewController:

    private var selectedItem: Item?
    
    private lazy var pickerViewPresenter: PickerViewPresenter = {
        let pickerViewPresenter = PickerViewPresenter()
        pickerViewPresenter.didSelectItem = { [weak self] item in
            self?.selectedItem = item
        }
        return pickerViewPresenter
    }()
    
    view.addSubview(pickerViewPresenter)
    
    // e.g. on a button tap
    @objc private func buttonTapped() {
        pickerViewPresenter.showPicker()
    }
    

    Example:

    I have attached it to a UITapGestureRecognizer.

    0 讨论(0)
  • 2021-02-05 16:46

    Attach a UIPickerView as the inputView of a 0-sized UITextField that you have added to your view.

            let picker = UIPickerView()
            picker.dataSource = self
            picker.delegate = self
    
            let dummy = UITextField(frame: CGRectZero)
            view.addSubview(dummy)
    
            dummy.inputView = picker
            dummy.becomeFirstResponder()
    
    0 讨论(0)
  • 2021-02-05 16:54

    It might not be your only option but animating the UIPickerView should be relatively easy for you to do. Add the picker view so it's displayed off the bottom of the screen. When it's time to animate it in:

    [UIView animateWithDuration:0.3 animations:^{
        self.datePicker.frame = CGRectMake(0, self.view.bounds.size.height - datePicker.bounds.size.height, datePicker.bounds.size.width, datePicker.bounds.size.height);
    }];
    

    And when it's time to hide it:

    [UIView animateWithDuration:0.3 animations:^{
        self.datePicker.frame = CGRectMake(0, self.view.bounds.size.height, datePicker.bounds.size.width, datePicker.bounds.size.height);
    }];
    
    0 讨论(0)
  • 2021-02-05 16:54

    I went with borisgolovnev's suggestion of an invisible UITextField and came up with the following Swift 2.3 implementation:

    import UIKit
    
    class PickerViewPresenter: UITextField {
    
        // MARK: - Initialization
    
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
    
        init() {
            super.init(frame: CGRect.zero)
        inputView = pickerView
        inputAccessoryView = pickerInputAccessoryView
        }
    
        // MARK: - Public
    
        var pickerDelegate: UIPickerViewDelegate? {
            didSet {
                pickerView.delegate = pickerDelegate
            }
        }
    
        var pickerDataSource: UIPickerViewDataSource? {
            didSet {
                pickerView.dataSource = pickerDataSource
            }
        }
    
        var selectButtonAction: (() -> Void)?
    
        var currentlySelectedRow: Int {
            return pickerView.selectedRowInComponent(0)
        }
    
        func selectRowAtIndex(index: Int) {
            pickerView.selectRow(index, inComponent: 0, animated: false)
        }
    
        func showPicker() {
            self.becomeFirstResponder()
        }
    
        func hidePicker() {
            self.resignFirstResponder()
        }
    
        // MARK: - Views
    
        private let pickerView = UIPickerView(frame: CGRect.zero)
    
        private lazy var pickerInputAccessoryView: UIView = {
            let frame = CGRect(x: 0.0, y: 0.0, width: 0.0, height: 48.0)
            let pickerInputAccessoryView = UIView(frame: frame)
    
            // Customize the view here
    
            return pickerInputAccessoryView
        }()
    
        func selectButtonPressed(sender: UIButton) {
            selectButtonAction?()
        }
    
    }
    

    PickerViewPresenter can then conveniently be used as such:

    class ViewController: UIViewController, UIPickerViewDataSource, UIPickerViewDelegate {
    
        override func viewDidLoad() {
            super.viewDidLoad()
            view.addSubview(pickerViewPresenter)
        }
    
        private let dataModel = [10, 20, 30, 40, 50]
    
        private lazy var pickerViewPresenter: PickerViewPresenter = {
            let pickerViewPresenter = PickerViewPresenter()
            pickerViewPresenter.pickerDelegate = self
            pickerViewPresenter.pickerDataSource = self
            pickerViewPresenter.selectButtonAction = { [weak self] () -> Void in
                guard let strongSelf = self else {
                    return
                }
                let result = strongSelf.dataModel[pickerViewPresenter.currentlySelectedRow]
                pickerViewPresenter.hidePicker()
                // ...
            }
    
            return pickerViewPresenter
        }()
    
        private func presentPickerView {
            let index = 0 // [0..dataModel.count-1]
            pickerViewPresenter.selectRowAtIndex(index)
            pickerViewPresenter.showPicker()
        }
    
        // MARK: - UIPickerViewDataSource
    
        func numberOfComponentsInPickerView(pickerView: UIPickerView) -> Int {
            return 1
        }
    
        func pickerView(pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
            return dataModel.count
        }
    
        // MARK: - UIPickerViewDelegate
    
        func pickerView(pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
            return "\(dataModel[row]) drunken sailors"
        }
    
    }
    

    Hope someone finds this useful =)

    0 讨论(0)
  • 2021-02-05 16:56

    A better option is still to use the UITextField. You don't have to actually show it on screen. Just put it in a 0x0 UIView so that it is not visible, set it's inputView to your UIPickerView and call becomeFirstResponder on it to show the picker and resignFirstResponder to hide it.

    It is convenient to have a UIView subclass that implements all of that along with UIPickerViewDelegate and UIPickerViewDataSource methods.

    0 讨论(0)
提交回复
热议问题