Search code examples
iosswiftuibuttonuipickerview

How do you add a button to a UIPickerView?


How do you add a single button to UIPickerView that can be used to dismiss or hide the PickerView?

I found a few solutions to these problems and many did not seem to come up with the answer I wanted. This question was the closest I could find to what I was asking, but it is very outdated so I wanted to display my solution. I have a subclass of UIPickerView that I wanted to add a UIButton to be able to dismiss on. I do not want a UIPickerView with a UIToolBar inside.

The below image describes exactly what I am looking for where the done button is added to my subclass of UIPickerView

enter image description here

It may seem trivial that all you need to do is add a UIButton to the UIPickerView and add a target to call on a method, because I also want the PickerView to respond to user selection on the rows, pressing the Done button caused no response


Solution

  • Create subclass of UIView as such

    class CustomViewWithPicker: UIView {
        
        let picker = UIPickerView(frame: .zero)
        let pickerTitle = UILabel(frame: .zero)
        let button = UIButton(frame: .zero)
        
        let title: String = "Picker Title"
        let buttonName: String = "Button"
        
        override init(frame: CGRect) {
            super.init(frame: frame)
            didLoad()
        }
        
        required init?(coder aDecoder: NSCoder) {
            super.init(coder: aDecoder)
            didLoad()
        }
        
        func didLoad() {
            
            self.addSubview(picker)
            self.addSubview(pickerTitle)
            self.addSubview(button)
            
            picker.backgroundColor = .tertiarySystemBackground
            picker.layer.cornerRadius = 20
            picker.frame = .zero
            
            pickerTitle.text = title
            pickerTitle.font = .boldSystemFont(ofSize: 22)
            pickerTitle.textAlignment = .center
            pickerTitle.backgroundColor = .tertiarySystemBackground
            
            button.setTitle(buttonName, for: .normal)
            button.contentHorizontalAlignment = .right
            button.contentVerticalAlignment = .top
            button.isSelected = true
            
            self.updateConstraints()
        }
        
        override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
            if self.point(inside: point, with: event) {
                return super.hitTest(point, with: event)
            }
            guard isUserInteractionEnabled, !isHidden, alpha > 0 else {
                return nil
            }
            
            for subview in subviews.reversed() {
                let convertedPoint = subview.convert(point, from: self)
                if let hitView = subview.hitTest(convertedPoint, with: event) {
                    return hitView
                }
            }
            return nil
        }
        
        override func layoutSubviews() {
            super.layoutSubviews()
        }
        
        override func updateConstraints() {
            // Make Constraints ...
        }
    }
    

    In the ViewController conform to UIPickerViewDelegate and UIPickerViewDataSource

    class MyViewController : UIViewController, UIPickerViewDelegate, UIPickerViewDataSource, UIGestureRecognizerDelegate {
        
        let customView = CustomViewWithPicker()
        let labels = ["label0", "label1", "label2", "label3", "label4", "label5"]
        var selectedRow = 0
    
        override func viewDidLoad() {
            super.viewDidLoad()
            
            customView.picker.delegate = self
            customView.picker.dataSource = self
    
            customView.button.addTarget(self, action: #selector(doneButtonTapped(_:)), for: .touchUpInside)
            
            self.view.addSubview(customView)
            
        }
        
        func numberOfComponents(in pickerView: UIPickerView) -> Int {
            1
        }
        
        func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
            return labels.count
        }
        
        func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
            return labels[row]
        }
        
        func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
            selectedRow = row
        }
        
        @objc func doneButtonTapped(_ selectedButton: UIButton) {
            if selectedButton.isSelected {
                print("Done Button Tapped")
            }
        }
    }
    

    I looked far and wide for a UIPickerView implementation that did not rely on UIToolBar to detect a tap on the button to no avail.

    Thank you to Duncan C. for the input and advice