I'm writing an iPhone app (Swift 4.0, iOS 10.3.3, Xcode 9.2) with a single main UIViewController
that contains a UIDatePicker
whose mode is the default UIDatePickerMode.dateAndTime
.
The user is intended to keep the app running in the background (though that's not necessary), and, from time to time, open it up, select a specific date & time, and click a button.
My problem is that the time-sensitive labels on the date part of the spinner, namely, "Today" and "Yesterday", do not get updated when the app is re-opened a day (or days) after it was originally launched and left in the background.
This means that, after a day or so of use, the UI will likely be in a confusing or inconsistent state. For instance, here's a screenshot from January 19:
It managed to repaint the "Yesterday" line as "Wed Jan 17", but didn't get around to rendering "Today" as "Yesterday", or "Fri Jan 19" as "Today.
Another, from around noon today, January 22 (the selected value is 9:29 AM, Jan 21):
"Yesterday" is correct, but "Today" is missing.
And sometimes the stale labels just pile up, as in this shot from January 14 (selected value is 10:47 PM, Jan 15):
If I force-quit and re-launch the app, everything looks new and fresh and correct, but I'd like to avoid having to do that.
I've tried calling setNeedsDisplay()
on the UIDatePicker
instance whenever viewWillAppear
is called, as well as whenever NotificationCenter
triggers a .UIApplicationWillEnterForeground
event, to no avail :(.
Most controls don't have internal state that's contingent on the current time, so I guess this doesn't come up often. But in this case, what are my options?
How do I trigger a full refresh of the UIDatePicker
control?
There seem to be three similar questions on SO already:
setDate(...)
on the UIDatePicker
with the current date value (in those cases where I call setNeedsDisplay()
). This seems rather obtuse, considering that I'm not changing the date (and I'm not sure how to test it for another 5 or so hours).setDate
(doesn't work; see 3.), and picker.becomeFirstResponder()
+ picker.reloadInputViews()
(doesn't work; see 6.)Edit: It's now January 23, 5 minutes after midnight. What have I tried:
setNeedsDisplay()
on the UIDatePicker whenever the view will appear / application enters foreground (I'll call this "on refresh").
The observations/screenshots above were all taken in this circumstance. I was having the same problem before I added setNeedsDisplay()
, and I didn't notice any change in behavior, but I wasn't being so analytical before I added it. Stale labels are an issue both with and without setNeedsDisplay()
.Setting the .date = ...
field directly "on refresh" (with the same stored Date that is used when viewDidLoad
is originally called). This had no direct effect (the current date is Jan 23):
but updated the underlying layer (?!) (here I'm actively mid-scroll):
I actually have two UIDatePickers on that same view, so I was able to test out setting the date via .setDate(..., animated: true)
as well, which worked a little better — "Jan 22" became "Yesterday", but there is no "Today" date ("Jan 23" remains "Jan 23"):
Breaking news! ~30 minutes later (12:35 AM), after backgrounding and re-opening the app once more, labels have again changed. In the case of setting .date
directly (2.), "Today" now appears and is correct, on both layers. With .setDate(...)
(3.), "Today" has been properly applied, but yesterday (on the grayed-out layer) is also "Today".
I'm guessing it must have been because, after the 12:05 AM date (re-)setting calls, I scrolled around a little, and then just now, at 12:30 AM, triggered the date setting calls again? But that's just a thought — scrolling around seems to have some weird side-effects even in realtime.
myDatePicker.locale = NSLocale.autoupdatingCurrent
(the default is NSLocale.current
). This seems to have no effect: the "Today" label is still wrong "on refresh".picker.becomeFirstResponder()
and/or picker.reloadInputViews()
"on refresh." Doesn't seem to have any effect.I don't think you can have both today and yesterday shown in your date picker but you can make your date picker update properly removing it from its Superview and setting it up again. Btw you can add an observer for NSCalendarDayChanged
to your view controller and add a selector to create a new date picker:
class ViewController: UIViewController {
var datePicker = UIDatePicker()
var date = Date()
override func viewDidLoad() {
super.viewDidLoad()
NotificationCenter.default.addObserver(self, selector: #selector(dayChanged), name: .NSCalendarDayChanged, object: nil)
setupDatePicker()
}
func setupDatePicker() {
DispatchQueue.main.async {
self.datePicker.removeFromSuperview()
self.datePicker = UIDatePicker()
self.datePicker.addTarget(self, action: #selector(self.datePickerChanged), for: .valueChanged)
self.datePicker.datePickerMode = .dateAndTime
self.datePicker.date = self.date
self.view.addSubview(self.datePicker)
}
}
@objc func datePickerChanged(_ datePicker: UIDatePicker) {
print("datePicker.date:", datePicker.date)
date = datePicker.date
}
@objc func dayChanged(_ notification: Notification) {
print("day changed:", Date().description(with: .current))
setupDatePicker()
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With