Search code examples
iosswiftxcodeuidatepicker

How to reset inline iOS UIDatePicker when no date is selected


When using the new inline UIDatePicker (Introduced in iOS 14). How can I reset the calendar to current date when no date is already selected.

When running the following, the displayed calendar will reset to the current date WHEN you have selected another date:

self.datePicker.setDate(Date(), animated: true)

However, if you create a new inline UIDatePicker and start scrolling through months, running this same line of code will not update the view until a specific date is selected by the user.


Solution

  • That's curious...

    My guess would be that when using the older "wheel" date picker style, a new date is selected when the wheels stop scrolling... there is no way to "scroll away" from the selected date.

    My second guess would be that this issue could be changed ("fixed") in a future iOS update.

    Anyway, here is one work-around...

    • get today's date
    • get the selected date from the picker
    • if they are the same, user may have scrolled the months / years, so
      • add one day to "today"
      • set the picker to "tomorrow" (animated)
      • make an async call to set the picker to "today"
    • if the dates are not the same, just set the picker to "today" (animated)

    I've only done a quick test of this, so you'll want to thoroughly test it. And probably add some additional checking -- such as what happens if the calendar rolls over midnight while the picker is displayed; how does it look if "today" is the last day of the month; etc.

        // get "today" date
        let today = Date()
    
        // get selected date
        let pickerDate = self.datePicker.date
        
        // are the dates the same day?
        let todayIsSelected = Calendar.current.isDate(today, inSameDayAs:pickerDate)
    
        if todayIsSelected {
            // picker has today selected, but may have scrolled months...
    
            // should never fail, but this unwraps the optional
            guard let nextDay = Calendar.current.date(byAdding: .day, value: 1, to: today) else {
                return
            }
    
            // animate to "tomorrow"
            self.datePicker.setDate(nextDay, animated: true)
    
            // async call to animate to "today"
            DispatchQueue.main.async {
                self.datePicker.setDate(today, animated: true)
            }
        } else {
            // picker has a different date selected
            //  so just animate to "today"
            self.datePicker.setDate(today, animated: true)
        }
        
    

    Edit - complete example:

    class ScratchVC: UIViewController {
        
        let datePicker = UIDatePicker()
        let btn = UIButton()
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            if #available(iOS 14.0, *) {
                datePicker.preferredDatePickerStyle = .inline
            } else {
                // Fallback on earlier versions
            }
            
            btn.backgroundColor = .red
            btn.setTitle("Go To Today", for: [])
            btn.setTitleColor(.white, for: .normal)
            btn.setTitleColor(.gray, for: .highlighted)
            
            btn.translatesAutoresizingMaskIntoConstraints = false
            datePicker.translatesAutoresizingMaskIntoConstraints = false
            
            view.addSubview(btn)
            view.addSubview(datePicker)
            
            let g = view.safeAreaLayoutGuide
            
            NSLayoutConstraint.activate([
                
                btn.topAnchor.constraint(equalTo: g.topAnchor, constant: 20.0),
                btn.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
                btn.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
                
                datePicker.widthAnchor.constraint(equalTo: g.widthAnchor, multiplier: 0.9),
                datePicker.centerXAnchor.constraint(equalTo: g.centerXAnchor),
                datePicker.centerYAnchor.constraint(equalTo: g.centerYAnchor),
                
            ])
            
            btn.addTarget(self, action: #selector(didTap(_:)), for: .touchUpInside)
            
        }
        
        @objc func didTap(_ sender: Any) {
            
            // get "today" date
            let today = Date()
    
            // get selected date
            let pickerDate = self.datePicker.date
            
            // are the dates the same day?
            let todayIsSelected = Calendar.current.isDate(today, inSameDayAs:pickerDate)
    
            if todayIsSelected {
                // picker has today selected, but may have scrolled months...
    
                // should never fail, but this unwraps the optional
                guard let nextDay = Calendar.current.date(byAdding: .day, value: 1, to: today) else {
                    return
                }
    
                // animate to "tomorrow"
                self.datePicker.setDate(nextDay, animated: true)
    
                // async call to animate to "today" - delay for 0.1 seconds
                DispatchQueue.main.asyncAfter(deadline: .now() + 0.1, execute: {
                    self.datePicker.setDate(today, animated: true)
                })
            } else {
                // picker has a different date selected
                //  so just animate to "today"
                self.datePicker.setDate(today, animated: true)
            }
            
        }
        
    }