0

我正在尝试编写一个属性包装器来访问 userDefaults 但有两个问题。代码片段如下

用户默认包装

@propertyWrapper
struct UserDefault<T> {
    private let key: String
    private let defaultValue: T
    private let userDefaults: UserDefaults

    init(key: String, defaultValue: T, userDefaults: UserDefaults = .standard) {
        self.key = key
        self.defaultValue = defaultValue
        self.userDefaults = userDefaults
    }

    var wrappedValue: T {
        get {
            return userDefaults.object(forKey: key) as? T ?? defaultValue
        }

        set {
            userDefaults.set(newValue, forKey: key)
        }
    }
}

可观察对象

final class AppSettings: ObservableObject {

    let objectWillChange = PassthroughSubject<Void, Never>()

    @UserDefault(key: "focusSliderValue", defaultValue: 25.0)
    var focusSliderValue: TimeInterval {
        willSet {
            objectWillChange.send()
        }
    }

    @UserDefault(key: "shortBreakLength", defaultValue: 5.0)
    var shortBreakLength: TimeInterval {
        willSet {
            objectWillChange.send()
        }
    }

    @UserDefault(key: "longBreakLength", defaultValue: 25.0)
    var longBreakLength: TimeInterval {
        willSet {
            objectWillChange.send()
        }
    }

    @UserDefault(key: "sessionsPerRound", defaultValue: 4)
    var sessionsPerRound: Int {
        willSet {
            objectWillChange.send()
        }
    }
}

上面的代码有效,但有以下问题

问题一:

我试图用@Published 关键字替换大部分样板代码,但是当我这样做时,我得到“'Double' 类型的值没有成员'wrappedValue'”错误,我无法理解/修复。

问题2:

保持代码不变,我使用步进器在我的设置屏幕上更新“focusSliderValue”,该屏幕在我的根视图上更新。如果我按下步进器一次并立即返回根视图得到正确更新但如果我按下步进器说 10 次然后返回根视图我会看到带有“...”(更新)符号的屏幕然后该字段几秒钟后更新。为什么这个过渡/可观察对象不平滑?

更新了问题 2 的 GIF

问题 2 的内容视图

struct ContentView: View {
    @State var to : CGFloat = 1
    @State private var isAnimating = false
    @ObservedObject var appSettings = AppSettings()

    let gradientColors = Gradient(colors: [Color.blue, Color.purple])
    var body: some View {
        NavigationView {
            ZStack{
                VStack(spacing: 50) {
                    ZStack {
                        Circle().trim(from: 0, to: 1)
                            .stroke(Color.black.opacity(0.09), style: StrokeStyle(lineWidth: 10, lineCap: .round))
                            .frame(width: 200, height: 200)
                        Circle()
                            .trim(from: 0, to: self.isAnimating ? self.to : 0)
                            .stroke(Color.black, style: StrokeStyle(lineWidth: 10, lineCap: .round))
                            .frame(width: 200, height: 200)
                            .rotationEffect(.init(degrees: -90))
                            .overlay(Text("\(Int(appSettings.focusSliderValue))").font(.title), alignment: .center)
                            .animation(Animation.linear(duration: 5).repeatCount(0, autoreverses: false))
                    }

                    Button(action: {self.isAnimating.toggle()}, label: {
                        Text("Start")
                            .font(.body)
                            .foregroundColor(.black)
                            .padding(40)
                    }).padding()
                        .clipShape(Circle())
                        .overlay(Circle().stroke(Color.black, style: StrokeStyle(lineWidth: 2, lineCap: .round)))
                }.padding()
            }.navigationBarTitle(Text("Pomodoro Timer"), displayMode: .automatic)
                .navigationBarItems(trailing: NavigationLink(destination: SettingsView(appSettings: appSettings), label: {
                    Image(systemName: "gear")
                        .font(.title)
                        .foregroundColor(.black)
                }))
        }
    }
}

struct SettingsView: View {
    @ObservedObject var appSettings: AppSettings

    var body: some View {
        Form {
            Section(header: Text("TIMER LENGTH")) {
                HStack {
                    Stepper(value: $appSettings.focusSliderValue , in: 0...60, step: 1.0) {
                        Text("Focus Length : \($appSettings.focusSliderValue.wrappedValue)" + " mins")
                            .fontWeight(.bold)
                            .foregroundColor(.black)
                    }

                }
                HStack {
                    Stepper(value: $appSettings.shortBreakLength) {
                        Text("Short break Length : \(($appSettings.shortBreakLength.wrappedValue))" + " mins")
                            .fontWeight(.bold)
                            .foregroundColor(.black)
                    }
                }
                HStack {
                    Stepper(value: $appSettings.longBreakLength) {
                        Text("Long break Length : \(($appSettings.longBreakLength.wrappedValue))" + " mins")
                            .fontWeight(.bold)
                            .foregroundColor(.black)
                    }
                }
            }.font(.caption)
        }
    }
}

struct ContentView_Previews: PreviewProvider {

    static var previews: some View {
        ContentView(appSettings: AppSettings())
    }
}

编辑:更新了问题 2 的礼物

4

1 回答 1

0

试试这个变种

final class AppSettings: ObservableObject {

    @UserDefault(key: "focusSliderValue", defaultValue: 25.0)
    var focusSliderValue: TimeInterval {
        didSet {
            // uncomment for multi-threaded use and make same for others
            // DispatchQueue.main.async { 
            self.objectWillChange.send() // << use default publisher !!
            // }
        }
    }

    @UserDefault(key: "shortBreakLength", defaultValue: 5.0)
    var shortBreakLength: TimeInterval {
        didSet {
            self.objectWillChange.send()
        }
    }

    @UserDefault(key: "longBreakLength", defaultValue: 25.0)
    var longBreakLength: TimeInterval {
        didSet {
            self.objectWillChange.send()
        }
    }

    @UserDefault(key: "sessionsPerRound", defaultValue: 4)
    var sessionsPerRound: Int {
        didSet {
            self.objectWillChange.send()
        }
    }
}
于 2020-06-07T06:19:09.253 回答