如何使用 SwiftUI 和 Combine 检测 Datepicker 的值变化?

Posted

技术标签:

【中文标题】如何使用 SwiftUI 和 Combine 检测 Datepicker 的值变化?【英文标题】:How to detect a value change of a Datepicker using SwiftUI and Combine? 【发布时间】:2020-02-24 11:41:01 【问题描述】:

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

我已经寻找特定方法来识别值更改(使用 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 的经验,因此我想寻求一些帮助......有什么想法吗? :)

非常感谢!

【问题讨论】:

这一切都可以使用绑定和状态来完成。能否也分享一下滑块和文本的代码? 当然,我在帖子中添加了更多代码。我正在做的是使用日期选择器轮选择日期,并更新显示年份周(从 1 到 53)和滑块位置(也代表年份周)的标签。同时,当我移动滑块时,我想更新选择器的状态(这最后一部分我已经可以做到了,因为我在滑块上使用'onEditingChanged'来调用我的更新方法,即使这只更新了选择器当我终止滑块的移动时,而我更喜欢持续更新)。我不能做相反的事情(更新滑块/laber)。 【参考方案1】:

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

这里是演示

这里是代码

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

【讨论】:

【参考方案2】:

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

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

【讨论】:

这很好,但是仅在 ios14+ 中可用,如果您在 iOS13 中需要此功能,请查看this article 中的最后一个示例。【参考方案3】:

另一种方法是,只要更改日期,就会调用 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()
        

    


【讨论】:

【参考方案4】:

使用 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()
    

【讨论】:

以上是关于如何使用 SwiftUI 和 Combine 检测 Datepicker 的值变化?的主要内容,如果未能解决你的问题,请参考以下文章

使用 Combine 和 SwiftUI 在 Realm 中观察收集结果

SwiftUI + Combine:如何将数据分配给带有动画的模型

使用 SwiftUI、Combine 和 Firebase,我如何在将用户的帐户链接到电子邮件/密码之前验证用户是不是已匿名登录?

如何使用 Combine 的 CurrentValueSubject 并在 SwiftUI 视图中访问它?

使用 SwiftUI/Combine,如何避免在 ViewModel 中放置可取消项

将 AppKit/UIKit 中的 Key-Value Observation 转换为 Combine 和 SwiftUI