Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Refresh internal state of iOS UIDatePicker after midnight

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:

Screenshot with wrong "Today" label

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):

Screenshot with wrong "Yesterday" label

"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):

Screenshot with multiple stale "Today" labels

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:

  • UIDatePicker does not update the 'Today' flag when the date advances. I'm not using Interface Builder, so I'm already doing what the selected answer advises (which isn't working)
  • Refresh iOS UIDatePicker whenever a view is reloaded or after period of time?, which advises calling 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).
  • UIDatePicker vs. UIApplicationSignificantTimeChangeNotification, which recommends wholesale replacement (brute force), 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:

  1. Call 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().
  2. 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):

    Screenshot with wrong "Today" label

    but updated the underlying layer (?!) (here I'm actively mid-scroll):

    "Lower layer gets updated, partially"

  3. 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"):

    "Yesterday" gets updated, no "Today"

    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.

  4. (As of January 24) Setting (once, when initializing) myDatePicker.locale = NSLocale.autoupdatingCurrent (the default is NSLocale.current). This seems to have no effect: the "Today" label is still wrong "on refresh".
  5. (As of January 25) Tried (4.) combined with (1.). No dice.
  6. Call picker.becomeFirstResponder() and/or picker.reloadInputViews() "on refresh." Doesn't seem to have any effect.
like image 469
chbrown Avatar asked Jan 23 '18 00:01

chbrown


1 Answers

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()
    }
}
like image 102
Leo Dabus Avatar answered Nov 15 '22 18:11

Leo Dabus