Search code examples
eventsdatepickerswiftuicombinevaluechangelistener

How to detect a value change of a Datepicker using SwiftUI and Combine?


How would you detect a change of value of a Datepicker while using SwiftUI and Combine? I need to invoke a method whenever the datepicker wheel is moved, to update a Text and a Slider.

I have looked for specific methods to identify the value change (using UIKit it was possible to associate an action to an event), but apparently I haven't found anything useful in the documentation (I've tried the onTapGesture methods, but that's not what I want, since it forces the user to tap the picker to update the other views, whereas I would like to have an automatic update whenever the user moves the wheel).

import SwiftUI

struct ContentView: View {

    private var calendar = Calendar.current

    @State private var date = Date()
    @State private var weekOfYear = Double(Calendar.current.component(.weekOfYear, from: Date()) )
    @State private var lastWeekOfThisYear = 53.0
    @State private var weekDay: String = { () -> String in 
        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "EEEE"
        let weekDay = dateFormatter.string(from: Date())
        return weekDay
    }()

    var body: some View {

        VStack {

            // Date Picker
            DatePicker(selection: $date, displayedComponents: .date, label:{ Text("Please enter a date") }
            )
            .labelsHidden()
            .datePickerStyle(WheelDatePickerStyle())
            .onTapGesture {
                self.updateWeekAndDayFromDate()
            }

            // Week number and day
            Text("Week \(Int(weekOfYear.rounded()))")
            Text("\(weekDay)")

            // Slider
            Slider(value: $weekOfYear, in: 1...lastWeekOfThisYear, onEditingChanged: { _ in
                    self.updateDateFromWeek()
                })
            }

    }

    func updateWeekAndDayFromDate() {
        // To do
    }

    func updateDateFromWeek() {
        // To do
    }

    func setToday() {
        // To do
    }

    func getWeekDay(_ date: Date) -> String {
        //To do
    }
}

I guess this could be solved using Combine (observableobject, published, sink, etc.), but I'm not experienced yet with Combine, therefore I'd like to ask for some help... any ideas? :)

Thanks a lot!


Solution

  • Please find below one possible approach (with a bit modified your code). Here is an idea to have source state transparent to activate dependent within it. Hope it will be helpful somehow.

    Here is demo

    dependent binding of DatePicker

    Here is code

    struct TestDatePicker: View {
    
        private static func weekOfYear(for date: Date) -> Double {
            Double(Calendar.current.component(.weekOfYear, from: date))
        }
    
        private static func weekDay(for date: Date) -> String {
            let dateFormatter = DateFormatter()
            dateFormatter.dateFormat = "EEEE"
            let weekDay = dateFormatter.string(from: date)
            return weekDay
        }
    
        @State private var date: Date
        @State private var weekOfYear: Double
        @State private var weekDay: String
        @State private var lastWeekOfThisYear = 53.0
    
        private var dateProxy:Binding<Date> {
            Binding<Date>(get: {self.date }, set: {
                self.date = $0
                self.updateWeekAndDayFromDate()
            })
        }
    
        init() {
            let now = Date()
            self._date = State<Date>(initialValue: now)
            self._weekOfYear = State<Double>(initialValue: Self.weekOfYear(for: now))
            self._weekDay = State<String>(initialValue: Self.weekDay(for: now))
        }
    
        var body: some View {
    
            VStack {
    
                // Date Picker
                DatePicker(selection: dateProxy, displayedComponents: .date, label:{ Text("Please enter a date") }
                )
                .labelsHidden()
                .datePickerStyle(WheelDatePickerStyle())
    
                // Week number and day
                Text("Week \(Int(weekOfYear.rounded()))")
                Text("\(weekDay)")
    
                // Slider
                Slider(value: $weekOfYear, in: 1...lastWeekOfThisYear, onEditingChanged: { _ in
                        self.updateDateFromWeek()
                    })
                }
    
        }
    
        func updateWeekAndDayFromDate() {
            self.weekOfYear = Self.weekOfYear(for: self.date)
            self.weekDay = Self.weekDay(for: self.date)
        }
    
        func updateDateFromWeek() {
            // To do
        }
    
        func setToday() {
            // To do
        }
    
        func getWeekDay(_ date: Date) -> String {
            ""
        }
    }
    
    struct TestDatePicker_Previews: PreviewProvider {
        static var previews: some View {
            TestDatePicker()
        }
    }