28

I've run into a weird effect that sure looks like a bug in iOS7 -- but often in the past, when I have thought I found a bug in Apple's APIs, it has turned out to be my own misunderstanding.

I have a UIDatePicker with datePickerMode = UIDatePickerModeCountDownTimer and minuteInterval = 5. I initialize the duration to 1 hour, and present it to the user, where it appears as a two-column picker with hours and minutes. (So far so good.)

The user is thinking "20 minutes," and so scrolls the Hour column to 0. At this point the picker reads 0 hours and 0 minutes, and iOS7 is not cool with that, so it automatically scrolls the minute wheel to 5. My UIControlEventValueChanged handler gets invoked, and the countDownDuration reads 5 minutes. (Still good.)

Now the user grabs the minute wheel and drags it to 20. AND... my UIControlEventValueChanged handler does not get called. (Bad.)

If I have some other event in the UI check the date picker at this point, I do see the countDownDuration is set to 20. But I had no way of knowing that the user changed it, at the moment it was changed. This is very repeatable: it always happens on the first change AFTER the picker refuses to be set to 0 (advancing itself to 5 minutes).

Note that this is in iOS7; it does not occur in iOS6 (perhaps because the picker there is perfectly content to be set to 0 minutes).

So... am I missing something here? Or is this a genuine bug in iOS7? And in the latter case, does anybody know a work-around better than having some timer periodically check the current interval?

Joe Strout
  • 2,634
  • 2
  • 28
  • 39

7 Answers7

35

I can also confirm that the iOS 7.0.3 UIDatePicker has a bug in it when used in UIDatePickerModeCountDownTimer mode. The picker does not fire the target-action associated with the UIControlEventValueChanged event the first time the user changes the value by scrolling the wheels. It works fine for subsequent changes.

Below is an efficient workaround. Simply enclose the code that sets the initial value of the countDownDuration in a dispatch block to the main loop. Your target-action method will fire every time the wheels are rotated to a new value. This approach has almost no overhead and works quite well on an iPhone 4 and iPad 4.

dispatch_async(dispatch_get_main_queue(), ^{
    self.myDatePicker.countDownDuration = (NSTimeInterval) aNewDuration ;
});

Swift 5:

DispatchQueue.main.async {
    self.myDatePicker.countDownDuration = aNewDuration
}
ManWithBear
  • 2,787
  • 15
  • 27
Positron
  • 2,371
  • 1
  • 14
  • 8
  • @Rich I can still reproduce the issue on iOS SDK 7.1 on an iPhone running iOS 7.1. What configuration do you have where this issue has been fixed? – Jamie A Mar 17 '14 at 15:56
  • That is quite weird! This bug still exists on iOS 7.1, but the fix in this answer worked for me. Thanks. – ThomasCle Jul 17 '14 at 06:53
  • 1
    I experience the same problem with Xcode6.1.1 and iOS 8.1 – neoneye Dec 07 '14 at 21:16
  • 1
    anyone know if a radar was opened for this? – linusthe3rd Dec 29 '14 at 00:26
  • Adding on to this, it looks like the above fix works fine in the 90% case. The issue still occurs if the picker is shown, a rotation occurs, and the user goes to change the value. – linusthe3rd Dec 31 '14 at 05:41
  • 3
    There is still a separate bug in iOS 9 which happens when you try to select the time at 00:00 (which is below my minimum interval of 10 minutes). So the date picker automatically switches to 00:10, after that, when you try to switch to 02:00 for example, the value changed event won’t get called. This happens every time the date picker goes below the interval. Here is the workaround. `- (IBAction)datePickerValueChanged:(UIDatePicker *)sender { if (sender.countDownDuration == (sender.minuteInterval * 60)) { sender.countDownDuration = sender.countDownDuration; } }` – Marc Etcheverry Dec 26 '15 at 09:44
6

I was still hitting this issue in 7.1 but adding the following to the UIControlEventValueChanged handler fixed it for me.

// Value Changed Event is not firing if minimum value hit
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.3 * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
    [self.myDatePicker setCountDownDuration: self.myDatePicker.countDownDuration];
});
Gareth
  • 2,051
  • 2
  • 19
  • 14
  • 1
    This worked for me under iOS 8, whereas Positron's solution didn't. – fatuhoku Mar 25 '15 at 18:05
  • same here, it seems the delay is needed – emma ray Jul 22 '15 at 17:31
  • This worked for me, or in the same approach, if you don't want to delay just run it in the main queue `dispatch_async(dispatch_get_main_queue(), ^{ [keyboard setCountDownDuration: keyboard.countDownDuration]; });` – Haroun SMIDA Jan 22 '19 at 13:20
2

If someone still having problems with datepicker... I'm using Swift / iOS 8 / Xcode 6.3

So solve the problem you should no use

picker.countDownDuration = NSTimeInterval

instead, use setDate

picker.setDate(NSDate, animated: true)

it works direct on viewDidLoad(), don't need to use `queues

2

For Swift 3:

DispatchQueue.main.async(execute: {
    yourPicker.countDownDuration = TimeInterval() 
})
laaksom
  • 2,050
  • 2
  • 18
  • 17
2

Since no answer has been accepted yet. The simplest solution that worked for me in swift 3 is to simply do

datePicker.countDownDuration = seconds

in viewDidAppear instead of viewDidLoad

0

If someone still having problems with datepicker... I'm using Swift / iOS 8 / Xcode 6.3

To solve the problem you should not use picker.countDownDuration = NSTimeInterval. Use .setDate(NSDate, animated: true).

it works direct on viewDidLoad(), don't need to use queues

The complete snippet:

override func viewDidLoad() {
    super.viewDidLoad()
    picker.setDate(setDateFromSeconds(seconds), animated: true)
}

func setDateFromSeconds(seconds: Double) -> (NSDate) {
    let intSeconds = Int(seconds)
    let minutes = (intSeconds / 60) % 60
    let hours = intSeconds / 3600
    let dateString = NSString(format: "%0.2d:%0.2d", hours, minutes)

    let dateFormatter = NSDateFormatter()
    dateFormatter.dateFormat = "hh:mm"
    return dateFormatter.dateFromString(dateString as String) as NSDate!
}
-2

Swift 4

@IBOutlet weak var fromPickerView: UIDatePicker!


@objc func toPickerViewDateChanged() {
        fromPickerView.minimumDate = toPickerView.date
    }
    override func viewDidLoad() {
        super.viewDidLoad()



toPickerView.backgroundColor = UIColor.white
    toPickerView.tintColor = .black
    toPickerView.maximumDate = Date()
    toPickerView.addTarget(self, action: 
    #selector(toPickerViewDateChanged), for: UIControlEvents.valueChanged)
binay
  • 1