Search code examples
iosswiftuidatepicker

UIDatePicker with toolbar but without text field


In my app, I have a button which when clicked should display time picker with toolbar on it. Most of examples I saw added toolbar as an inputAccessoryView on text field, but in my case I don't have a text field.

So, I created a custom view which has date time picker and toolbar and I am adding that view as a subview to my controller's view, but I don't see the custom view on the app.

Below is the controller code for button clicked :

func buttonClicked(date: Date) {
    let timePicker = EditTimeHelper.createTimePickerAndToolbar(displayDate: date)
    self.view.addSubview(timePicker)
}

Code for custom view in separate EditTimeHelper class:

  static func createTimePickerAndToolbar(displayDate: Date) -> UIView {
        let pickerView = UIView(frame: CGRect(x: 0, y: UIScreen.main.bounds.height - 300, width: UIScreen.main.bounds.width, height: 300))
        let timePicker = createTimePicker(displayDate: displayDate)
        pickerView.addSubview(timePicker)
        let toolbar = createUIToolBar()
        pickerView.addSubview(toolbar)
        return pickerView
    }
    
    static func createTimePicker(displayDate: Date) -> UIDatePicker {
        let timePicker:UIDatePicker = UIDatePicker()
        timePicker.datePickerMode = UIDatePicker.Mode.time
        timePicker.date = displayDate
        timePicker.minuteInterval = 15
        if #available(iOS 13.4, *) {
            timePicker.preferredDatePickerStyle = .wheels
        } else {
            // Fallback on earlier versions where time picker is wheels style by default.
        }
        timePicker.addTarget(self, action: #selector(timeChanged(_:)), for: UIControl.Event.valueChanged)
        timePicker.backgroundColor = .white
        timePicker.frame = CGRect(x: 0, y: UIScreen.main.bounds.height - 200, width: UIScreen.main.bounds.width, height: 200)
        return timePicker
    }
    
    private static func createUIToolBar() -> UIToolbar {
        let pickerToolbar = UIToolbar()
        pickerToolbar.autoresizingMask = .flexibleHeight
        
        //customize the toolbar
        pickerToolbar.barStyle = .default
        pickerToolbar.barTintColor = UIColor.black
        pickerToolbar.backgroundColor = UIColor.white
        pickerToolbar.isTranslucent = false
        
        pickerToolbar.frame = CGRect(x: 0, y: UIScreen.main.bounds.height - 300, width: UIScreen.main.bounds.width, height: 100)
        // add buttons
        let cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelBtnClicked(_:)))
        cancelButton.tintColor = UIColor.white
        let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
        let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneBtnClicked(_:)))
        doneButton.tintColor = UIColor.white
        
        //add the items to the toolbar
        pickerToolbar.items = [cancelButton, flexSpace, doneButton]
        return pickerToolbar
    }
    
    @objc func timeChanged(_ sender: UIDatePicker) {
       
    }
    
    @objc func cancelBtnClicked(_ button: UIBarButtonItem?) {
        
    }
    
    @objc func doneBtnClicked(_ button: UIBarButtonItem?) {
        
    }

Any idea what am I doing wrong and not seeing custom view?

If I call EditTimeHelper.createTimePicker(displatDate: date), then I see the time picker, but I want to add toolbar on top of it.

When I debug this code, I do see time picker and toolbar as custom view's subviews, but I just don't see them on the app.

enter image description here


Solution

  • The reason why you can't see the picker and the tool bar is because you have positioned the time picker and the tool bar incorrectly. Notice these two lines:

    timePicker.frame = CGRect(x: 0, y: UIScreen.main.bounds.height - 200, width: UIScreen.main.bounds.width, height: 200)
    // and
    pickerToolbar.frame = CGRect(x: 0, y: UIScreen.main.bounds.height - 300, width: UIScreen.main.bounds.width, height: 100)
    

    Since these are subviews of the pickerView, the coordinates are relative to the top left corner of pickerView, not the top left corner of the screen. You should instead do

    timePicker.frame = CGRect(x: 0, y: 100, width: UIScreen.main.bounds.width, height: 200)
    // and
    pickerToolbar.frame = CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 100)
    

    Now you should see the tool bar and the time picker.

    There are other problems with your code, however. First, timeChanged, cancelBtnClicked and doneBtnClicked won't be called. You have added self as the target for the bar button items and the picker, but since you are in a static method, self refers to the class itself. When the user presses the done button, it would try to call a method called doneBtnClicked on the class, rather than a particular instance. But the class doesn't have such a method! The doneBtnClicked you have declared is an instance method, available on instances only.

    Second, you are giving these views fixed positions. This means that the layout will look very weird when the user rotates the screen. Just use AutoLayout!

    You can make timeChanged, cancelBtnClicked and doneBtnClicked all static too, but a much better way is to just create a custom UIView subclass. Here is an example, as a starting point:

    class TimePickerToolBarView: UIView {
        override init(frame: CGRect) {
            super.init(frame: frame)
            commonInit()
        }
        
        required init?(coder: NSCoder) {
            super.init(coder: coder)
            commonInit()
        }
        
        private func commonInit() {
            let timePicker = createTimePicker()
            addSubview(timePicker)
            let toolBar = createUIToolBar()
            addSubview(toolBar)
            timePicker.translatesAutoresizingMaskIntoConstraints = false
            toolBar.translatesAutoresizingMaskIntoConstraints = false
            NSLayoutConstraint.activate([
                toolBar.heightAnchor.constraint(equalToConstant: 44),
                toolBar.topAnchor.constraint(equalTo: topAnchor),
                toolBar.leftAnchor.constraint(equalTo: leftAnchor),
                toolBar.rightAnchor.constraint(equalTo: rightAnchor),
                timePicker.leftAnchor.constraint(equalTo: leftAnchor),
                timePicker.rightAnchor.constraint(equalTo: rightAnchor),
                timePicker.bottomAnchor.constraint(equalTo: bottomAnchor),
                timePicker.topAnchor.constraint(equalTo: toolBar.bottomAnchor),
            ])
        }
        
        private func createTimePicker() -> UIDatePicker {
            let timePicker:UIDatePicker = UIDatePicker(frame: .zero)
            timePicker.datePickerMode = UIDatePicker.Mode.time
            timePicker.minuteInterval = 15
            if #available(iOS 13.4, *) {
                timePicker.preferredDatePickerStyle = .wheels
            } else {
                // Fallback on earlier versions where time picker is wheels style by default.
            }
            timePicker.addTarget(self, action: #selector(timeChanged(_:)), for: UIControl.Event.valueChanged)
            timePicker.backgroundColor = .white
            return timePicker
        }
        
        private func createUIToolBar() -> UIToolbar {
            let pickerToolbar = UIToolbar(frame: .zero)
            
            //customize the toolbar
            pickerToolbar.barStyle = .default
            pickerToolbar.barTintColor = UIColor.black
            pickerToolbar.backgroundColor = UIColor.white
            pickerToolbar.isTranslucent = false
            // add buttons
            let cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancelBtnClicked(_:)))
            cancelButton.tintColor = UIColor.white
            let flexSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil)
            let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(doneBtnClicked(_:)))
            doneButton.tintColor = UIColor.white
            
            //add the items to the toolbar
            pickerToolbar.items = [cancelButton, flexSpace, doneButton]
            return pickerToolbar
        }
        
        @objc func timeChanged(_ sender: UIDatePicker) {
           
        }
        
        @objc func cancelBtnClicked(_ button: UIBarButtonItem?) {
            
        }
        
        @objc func doneBtnClicked(_ button: UIBarButtonItem?) {
            
        }
    }