如何使用 SwiftUI 和 Combine 检测日期选择器的值变化?

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

在使用 SwiftUI 和 Combine 时,您如何检测 Datepicker 值的变化? 每当移动日期选择器轮时,我都需要调用一个方法来更新文本和滑块。

我一直在寻找特定的方法来识别值的变化(使用 UIKit 可以将一个动作与一个事件相关联),但显然我没有在文档中找到任何有用的东西(我试过 onTapGesture方法,但这不是我想要的,因为它会强制用户点击选择器来更新其他视图,而我希望在用户移动滚轮时自动更新)。

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
    }
}

我想这可以使用 Combine(observableobject、published、sink 等)来解决,但我还没有使用 Combine 的经验,因此我想寻求一些帮助...有什么想法吗? :)

非常感谢!

请在下面找到一种可能的方法(稍微修改一下您的代码)。这是一个让源状态透明以激活其中的依赖的想法。希望能有所帮助。

这是演示

这是代码

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 = [=10=]
            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()
    }
}

一种使用 ObservableObject 的可能方法

class TestPikerModel: ObservableObject {
    @Published var expireDate: Date = Date()
}

struct TestPikerView: View {

    @ObservedObject var testPikerModel = TestPikerModel()
    @State private var weekOfYear = Double(Calendar.current.component(.weekOfYear, from: Date()) )

    var body: some View {
    
        VStack {
            Text("\(Int(weekOfYear))")
            DatePicker("", selection: $testPikerModel.expireDate, displayedComponents: .date)
            .datePickerStyle(CompactDatePickerStyle())
            .clipped()
            .labelsHidden()
        }
        .onReceive(testPikerModel.$expireDate) { date in
            weekOfYear = Double(Calendar.current.component(.weekOfYear, from: date))
            updateWeekAndDayFromDate()
        }
    }

    func updateWeekAndDayFromDate() {
        print("updateWeekAndDayFromDate performed")
    }
}

struct TestPikerView_Previews: PreviewProvider {
    static var previews: some View {
        TestPikerView()
    }
}

另一种方法是,每当日期更改时调用 viewModel 中的观察者

class TestPikerModel: ObservableObject {
    @Published var expireDate: Date = Date()

init() {
       setupObserver()
    }
    
private func setupObserver() {
        $expireDate
            .sink { date in
                // do some networking call
            }
            .store(in: &cancellables)
    }
}

struct TestPikerView: View {

    @ObservedObject var testPikerModel = TestPikerModel()
    @State private var weekOfYear = Double(Calendar.current.component(.weekOfYear, from: Date()) )

    var body: some View {
    
        VStack {
            Text("\(Int(weekOfYear))")
            DatePicker("", selection: $testPikerModel.expireDate, displayedComponents: .date)
            .datePickerStyle(CompactDatePickerStyle())
            .clipped()
            .labelsHidden()
        }

    }

}

更简单的方法是在 DatePicker 上使用 .onChange:

DatePicker(
    "Datum",
    selection: $date,
    displayedComponents: [.date]
).onChange(of: date, perform: { value in
     // Do what you want with "date", like array.timeStamp = date
});