I'm using the code below to learn about Date Picker. It works, but the weird thing is the expected changes will not happen when I scroll the date picker for the first time. After that it will work normally. Wonder what happened.
import UIKit
import Foundation
class ViewController: UIViewController {
// Place this code inside your ViewController or where you want ;)
var txtField: UITextField = UITextField(frame: CGRect(x: 100, y: 0, width: 300, height: 50))
override func viewDidLoad() {
super.viewDidLoad()
// date picker setup
let datePickerView:UIDatePicker = UIDatePicker()
// choose the mode you want
// other options are: DateAndTime, Time, CountDownTimer
datePickerView.datePickerMode = UIDatePickerMode.countDownTimer
// choose your locale or leave the default (system)
//datePickerView.locale = NSLocale.init(localeIdentifier: "it_IT")
datePickerView.addTarget(self, action: #selector(onDatePickerValueChanged(_:)), for: UIControlEvents.valueChanged)
txtField.inputView = datePickerView
// datepicker toolbar setup
let toolBar = UIToolbar()
toolBar.barStyle = UIBarStyle.default
toolBar.isTranslucent = true
let space = UIBarButtonItem(barButtonSystemItem: UIBarButtonSystemItem.flexibleSpace, target: nil, action: nil)
let doneButton = UIBarButtonItem(title: "Done", style: UIBarButtonItemStyle.done, target: self, action: #selector(ViewController.doneDatePickerPressed))
// if you remove the space element, the "done" button will be left aligned
// you can add more items if you want
toolBar.setItems([space, doneButton], animated: false)
toolBar.isUserInteractionEnabled = true
toolBar.sizeToFit()
txtField.inputAccessoryView = toolBar
self.view.addSubview(txtField)
}
func doneDatePickerPressed(){
self.view.endEditing(true)
}
func onDatePickerValueChanged(_ sender: UIDatePicker) {
let (h, m, _) = secondsToHoursMinutesSeconds (duration: sender.countDownDuration)
self.txtField.text = String("\(h) Hours \(m) Minutes")
}
func secondsToHoursMinutesSeconds (duration : Double) -> (Int, Int, Int) {
let seconds:Int = Int(duration)
return (seconds / 3600, (seconds % 3600) / 60, (seconds % 3600) % 60)
}
}
Strange. This appears to be a bug in date pickers that are set to UIDatePickerMode.countDownTimer
mode.
You can get around most of the fail cases by implementing the UITextFieldDelegate
method textFieldShouldBeginEditing(_:)
and setting the picker's value after a short delay. Here is the code from my test project. (In my project the time interval field is called optionalTimeField
, and the time interval value is an optional called optionalTime
. You'd need to change it to fit your code:
See new function textField(_:,shouldChangeCharactersInRange:,replacementString:) below
extension ViewController: UITextFieldDelegate {
//Add this function to prevent keyboard editing
func textField(_ textField: UITextField,
shouldChangeCharactersIn range: NSRange,
replacementString string: String) -> Bool {
return false
}
func textFieldShouldBeginEditing(_ textField: UITextField) -> Bool {
if textField === optionalTimeField {
//Set the time interval of the date picker after a short delay
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.datePickerView.countDownDuration = self.optionalTime ?? 60
}
}
return true
}
}
I would suggest filing a bug report with Apple bug reporter. Based on the thread @Santosh linked, it looks like this bug has been in the UIDatePicker
since iOS 7, which is bad.
One bug that I don't see a work-around for is that if you try to select a time interval of 0 hours, 0 minutes, it switches to 1 minute, and after that, the next time interval you select does not trigger the value changed method.
There are various strange things about the interval date picker, like the fact that you can't specify it to choose seconds. If it's important enough, you might want to create your own time interval picker based on a generic UIPickerView
. It shouldn't be that hard. (Especially if you don't need to worry about localization to multiple locales that use different time formats.) You'd just create a picker view with spinners for hour, minute, and (optionally) seconds, populate it with your legal values, and away you'd go. You could set it up to have optional minimum and maximum interval values.