首页 > 解决方案 > 通过 @Published var 上的 didSet 将日期/时间保存到 UserDefaults(来自 SwiftUI 的 TimePicker 组件)

问题描述

我正在尝试保存用户的签到时间偏好(向他们发送通知提醒)。到目前为止,我已经能够将 Bool 和字符串保存到 UserDefaults 但我无法弄清楚如何保存时间偏好。这是我的 UserSettings 类来存储首选项。

public class UserSettings: ObservableObject {
    
    
    @Published var eveningCheckin: Bool {
        didSet{
            UserDefaults.standard.set(eveningCheckin, forKey: "eveningCheckin")
            print("Evening checkin toggle value in didSET to \(self.eveningCheckin)")
            
        }
    }
    
    @Published var eveningCheckinTime: Date {
        didSet{
            UserDefaults.standard.set(eveningCheckinTime, forKey: "eveningCheckinTime")
            print("Evening checkin didSet to \(self.eveningCheckinTime)")
        }
    }
    
    
    init() {

        self.eveningCheckin = UserDefaults.standard.object(forKey: "eveningCheckin") as? Bool ?? false
        self.eveningCheckinTime = UserDefaults.standard.object(forKey: "eveningCheckinTime") as? Date ?? Date(timeIntervalSince1970: 64800)
        print("Evening checkin time init to \(self.eveningCheckinTime)")// --> To debug
        print("Evening checkin toggle value in init to \(self.eveningCheckin)")
    }
}

我想使用 SwiftUI 提供的这样的时间选择器来设置这个晚上的签到时间。这是我的设置和时间选择器视图。

import SwiftUI

struct SettingsMain: View {
    @ObservedObject var userSettings = UserSettings()
    @State private var showTimepickerEvening = false

    var body: some View {
        NavigationView{
            ScrollView {
                VStack {
                    //Evening checkins
                    Button(action: {
                        //Show  Evening time picker sheet
                        self.showTimepickerEvening.toggle()
                    }) {
                        HStack {
                            Text("\(userSettings.eveningCheckinTime.hour12):\(userSettings.eveningCheckinTime.minute0x) \(userSettings.eveningCheckinTime.amPM.lowercased()) ")
                            Text("Change >")
                        }
                    }
                    .sheet(isPresented: $showTimepickerEvening) {
                        //Sheet view with the Timepicker
                        TimePickerView(pickedTime: self.$userSettings.eveningCheckinTime)
                    }
                }
                .buttonStyle(PlainButtonStyle())
            }
            .navigationBarTitle("Preferences", displayMode: .inline)
        }
    }
}

struct TimePickerView: View {
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
    @Binding var pickedTime: Date
    
    var body: some View {
        VStack {
            DatePicker("Checkin time", selection: $pickedTime, displayedComponents: .hourAndMinute)
            .labelsHidden()
            
            Button(action: {
                    //Dimiss. Should I actually update my UserDefaults here as well?
                self.presentationMode.wrappedValue.dismiss()
                }) {
                    ZStack {
                        ColorManager.buttonGrey
                        Text("Save time")
                            .font(.system(size: 20))
                            .fontWeight(.semibold)
                    }
                    .frame(height: 64)
                }
            .padding(.all)
            .buttonStyle(PlainButtonStyle())
        }
        .background(Color.white)
    }
}

我的问题是时间选择器确实将时间设置到 @Published var 但它会被 init 语句重置。布尔值不会发生这种情况。为什么??

这是上面 print() 语句的输出:基本上意味着每次我尝试使用 Datepicker 设置时间 -> didSet 确实选择了新时间,但 init() 将其重置为默认值。

晚上签到设置为 1970-01-01 12:12:00 +0000

晚上入住时间开始到 1970-01-01 18:00:00 +0000

晚上签入将 init 中的值切换为 true

晚上签到设置为 1970-01-01 12:13:00 +0000

晚上入住时间开始到 1970-01-01 18:00:00 +0000

晚上签入将 init 中的值切换为 true

标签: swiftswiftuicombine

解决方案


您如何存储和注释视图模型的问题。您永远不应该@ObservedObjectView本身中创建一个,而是注入它。每当@ObservedObject' 的@Published属性发生变化时,View存储该对象的 将被重新加载 - 这意味着如果您在@ObservedObject内部初始化它View,将创建该对象的新实例。

您需要将对象注入到您的视图中,以避免每次刷新视图时都重新创建它。

struct SettingsMain: View {
    @ObservedObject var userSettings: UserSettings
    @State private var showTimepickerEvening = false
...

从你创建的任何地方SettingsMain(我们称之为MainView插图),UserSettings在那里创建并注释它@State(或者如果你是从一个以外的东西创建它View- 比如说一个 ViewModel,然后让它@Published):

struct MainView: View {
    @State var userSettings = UserSettings()

    var body: some View {
        SettingsMain(userSettings: userSettings)
    }
}

推荐阅读