iosswiftuidatepicker

Open DatePicker popup directly on button tap


Trying to open the Date picker popup on button tap but it is showing a label instead of picker, and on tapping that label the picker opens. Below is code:

 @IBAction func changeMonth(_ sender: Any) {
     
    let picker : UIDatePicker = UIDatePicker()
    picker.datePickerMode = UIDatePicker.Mode.date
    picker.addTarget(self, action: #selector(dueDateChanged(sender:)), for: UIControl.Event.valueChanged)
        let pickerSize : CGSize = picker.sizeThatFits(CGSize.zero)
        picker.frame = CGRect(x:0.0, y:250, width:pickerSize.width, height:460)
        
        self.view.addSubview(picker)
}

@objc func dueDateChanged(sender:UIDatePicker){
    let dateFormatter = DateFormatter()
    dateFormatter.dateStyle = .none
    dateFormatter.timeStyle = .none
btnMonth.setTitle(dateFormatter.string(from: sender.date), for: .normal)
}

Need this to open on button tap:

enter image description here

But this is being shown:

enter image description here

and on tapping of this date label, picker opens. I am not getting why picker not opening directly. Please guide what's wrong in above code.


Solution

  • Here is a very simple example of embedding the date picker in a view, then showing / hiding that view on a button tap:

    class MyDatePicker: UIView {
        
        var changeClosure: ((Date)->())?
        var dismissClosure: (()->())?
    
        let dPicker: UIDatePicker = {
            let v = UIDatePicker()
            return v
        }()
        override init(frame: CGRect) {
            super.init(frame: frame)
            commonInit()
        }
        required init?(coder: NSCoder) {
            super.init(coder: coder)
            commonInit()
        }
        func commonInit() -> Void {
            let blurEffect = UIBlurEffect(style: .dark)
            let blurredEffectView = UIVisualEffectView(effect: blurEffect)
    
            let pickerHolderView: UIView = {
                let v = UIView()
                v.backgroundColor = .white
                v.layer.cornerRadius = 8
                return v
            }()
            
            [blurredEffectView, pickerHolderView, dPicker].forEach { v in
                v.translatesAutoresizingMaskIntoConstraints = false
            }
    
            addSubview(blurredEffectView)
            pickerHolderView.addSubview(dPicker)
            addSubview(pickerHolderView)
            
            NSLayoutConstraint.activate([
                
                blurredEffectView.topAnchor.constraint(equalTo: topAnchor),
                blurredEffectView.leadingAnchor.constraint(equalTo: leadingAnchor),
                blurredEffectView.trailingAnchor.constraint(equalTo: trailingAnchor),
                blurredEffectView.bottomAnchor.constraint(equalTo: bottomAnchor),
    
                pickerHolderView.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 20.0),
                pickerHolderView.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -20.0),
                pickerHolderView.centerYAnchor.constraint(equalTo: centerYAnchor),
    
                dPicker.topAnchor.constraint(equalTo: pickerHolderView.topAnchor, constant: 20.0),
                dPicker.leadingAnchor.constraint(equalTo: pickerHolderView.leadingAnchor, constant: 20.0),
                dPicker.trailingAnchor.constraint(equalTo: pickerHolderView.trailingAnchor, constant: -20.0),
                dPicker.bottomAnchor.constraint(equalTo: pickerHolderView.bottomAnchor, constant: -20.0),
    
            ])
            
            if #available(iOS 14.0, *) {
                dPicker.preferredDatePickerStyle = .inline
            } else {
                // use default
            }
            
            dPicker.addTarget(self, action: #selector(didChangeDate(_:)), for: .valueChanged)
            
            let t = UITapGestureRecognizer(target: self, action: #selector(tapHandler(_:)))
            blurredEffectView.addGestureRecognizer(t)
        }
        
        @objc func tapHandler(_ g: UITapGestureRecognizer) -> Void {
            dismissClosure?()
        }
        
        @objc func didChangeDate(_ sender: UIDatePicker) -> Void {
            changeClosure?(sender.date)
        }
        
    }
    
    class ViewController: UIViewController {
        
        let myPicker: MyDatePicker = {
            let v = MyDatePicker()
            return v
        }()
        let myButton: UIButton = {
            let v = UIButton()
            v.setTitle("Show Picker", for: [])
            v.setTitleColor(.white, for: .normal)
            v.setTitleColor(.lightGray, for: .highlighted)
            v.backgroundColor = .blue
            return v
        }()
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            [myButton, myPicker].forEach { v in
                v.translatesAutoresizingMaskIntoConstraints = false
                view.addSubview(v)
            }
            let g = view.safeAreaLayoutGuide
            NSLayoutConstraint.activate([
                
                // custom picker view should cover the whole view
                myPicker.topAnchor.constraint(equalTo: g.topAnchor),
                myPicker.leadingAnchor.constraint(equalTo: g.leadingAnchor),
                myPicker.trailingAnchor.constraint(equalTo: g.trailingAnchor),
                myPicker.bottomAnchor.constraint(equalTo: g.bottomAnchor),
                
                myButton.centerXAnchor.constraint(equalTo: g.centerXAnchor),
                myButton.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
                myButton.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.75),
                
            ])
            
            // hide custom picker view
            myPicker.isHidden = true
            
            // add closures to custom picker view
            myPicker.dismissClosure = { [weak self] in
                guard let self = self else {
                    return
                }
                self.myPicker.isHidden = true
            }
            myPicker.changeClosure = { [weak self] val in
                guard let self = self else {
                    return
                }
                print(val)
                // do something with the selected date
            }
            
            // add button action
            myButton.addTarget(self, action: #selector(tap(_:)), for: .touchUpInside)
        }
        
        @objc func tap(_ sender: Any) {
            myPicker.isHidden = false
        }
        
    }
    

    Here's how it looks on start:

    enter image description here

    tapping the button will show the custom view:

    enter image description here

    and here's how it looks on iOS 13 (prior to the new UI):

    enter image description here