UIDatePicker as countdown not triggering Value Changed event

Hi


I have created a UIDatePicker in count down mode and have assigned a method to the Value Changed event which prints the value of .countDownDuration.


It the general case, it works correctly and reports the correct value with each change. However, specifically, if I select the 0 hrs 0 min condition at any stage (which automatically rolls up to 0 hrs 1 min) the Value Changed call fails when I next select a value. If I select a new value again, it resumes working.


e.g.

Set picker to 10 mins - output is 600

Set picker to 5 mins - output is 300

Set picker to 0 mins, picker rolls up to 1 min automatically - output is 60

Set picker to 2 mins - Value Changed method isn't triggered - no output

Set picker to 3 mins - output is 180


I know this behaviour has been reported on other developer websites for previous versions of Swift however I have not yet found a way to resolve this issue.


Applies to Swift 3 in XCode 8.1.


Many thanks for any insight you can provide 🙂



Tim

Hi! It's funny. Today is 2 March of 2018, and i also see this bug.....Did you find a solution?

Please file a bug at https://bugreport.apple.com

I found a solution to the problem that the UIDatePicker control, when datePickerMode = .countDownDuration, does not fire a valueChanged event the first time the value is changed by the user.


I tried setting it and then changing it viewDidLoad, but that had no effect. However, this works for me:


override func viewDidAppear(_ animated: Bool) {

let countDownDuration = timeToLivePicker.countDownDuration

timeToLivePicker.countDownDuration = countDownDuration

}


After adding the above to my view controller, the control now fires the valueChanged event the first time the user changes it.

I have found the same issue while trying to create a UIViewRepresentable for a UIDatePicker while working with a countdown timer.
The workaround found by @brianfrombrighton did not work for me. What did work was setting the minuteInterval.

This is my UIViewRepresentable

Code Block
struct PickerView: UIViewRepresentable {
    @Binding var selection: Double    
    func makeUIView(context: Context) -> UIDatePicker  {
        let picker = UIDatePicker()
        picker.datePickerMode = .countDownTimer
        picker.preferredDatePickerStyle = .automatic
        picker.minuteInterval = 5
        picker.countDownDuration = selection
        picker.addTarget(context.coordinator, action: #selector(PickerView.Coordinator.didSelectTime), for: .allEvents)
        return picker
    }
    func updateUIView(_ uiView: UIDatePicker, context: Context) {
    }
    func makeCoordinator() -> PickerView.Coordinator {
        Coordinator(self)
    }
    class Coordinator: NSObject, UIPickerViewDelegate {
        var parent: PickerView
        init(_ pickerView: PickerView) {
            self.parent = pickerView
        }
        @IBAction func didSelectTime(sender: UIDatePicker, forEvent event: UIEvent) {
            parent.selection = sender.countDownDuration
        }
    }
}


Hope it helps anyone in the future!
Thanks. Could you also file a bug report ? That could speed resolution (and please mention the FA reference here).

I've been trying to implement this in SwiftUI but I've been having the same problem where it won't update the selection the first time. I've tried the solution above but they didn't work for me. Does anyone have any ideas on how you'd implement this in SwiftUI, and what would I set the Binded variable in the view as?

This is my Code:

import SwiftUI

struct DurationPicker: UIViewRepresentable {
    @Binding var duration: Double

    func makeUIView(context: Context) -> UIDatePicker {
        let datePicker = UIDatePicker()
        datePicker.datePickerMode = .countDownTimer
        datePicker.addTarget(context.coordinator, action: #selector(Coordinator.updateDuration), for: .valueChanged)
        return datePicker
    }

    func updateUIView(_ datePicker: UIDatePicker, context: Context) {
        datePicker.countDownDuration = duration
        
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    class Coordinator: NSObject {
        let parent: DurationPicker

        init(_ parent: DurationPicker) {
            self.parent = parent
        }

        @objc func updateDuration(datePicker: UIDatePicker) {
            parent.duration = datePicker.countDownDuration
        }
    }
}

struct ContentView: View {
    
    @State var duration = 60.0
    @State var timeRemaining = 60.0
    @State private var isActive = false
    var timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()
    
    
    var body: some View {
        VStack {
            Text("\(timeRemaining)")
                .bold()
                .font(.largeTitle)
            DurationPicker(duration: $duration)
            HStack {
                Button("Cancel") {
                    isActive = false
                }
                .padding()
                Button("Start") {
                    self.timeRemaining = duration
                    isActive = true
                }
                .padding()
            }
        }
        .onReceive(timer) { time in
            guard self.isActive else { return }
            
            if self.timeRemaining > 0 {
                self.timeRemaining -= 1
            }
        }
        .onReceive(NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification)) { _ in
            self.isActive = false
        }
        .onReceive(NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in
            self.isActive = true
        }

    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Hi guys,

I was able to fix this issue in SwiftUI (using a "UIDatePicker" created with "UIViewRepresentable") by adding it an "onAppear" and inside the closure updating the state variable used for tracking the duration to any value that is not the default value. This forces a programmatic update on the first render which then allows the picker to work correctly on the first spin.

UIDatePicker as countdown not triggering Value Changed event
 
 
Q