简体   繁体   English

SwiftUI:如何持久化@EnvironmentObject变量?

[英]SwiftUI: How to persist an @EnvironmentObject variable?

I want a variable to be an EnvironmentObject and I also want it to be persisted, so that it's the same every time that I relaunch my app. 我希望变量是一个EnvironmentObject,而且我也希望它能够持久化,因此每次我重新启动应用程序时它都是相同的。

To achieve that, I have already created the following propertyWrapper: 为此,我已经创建了以下propertyWrapper:

import Foundation

@propertyWrapper
struct UserDefault<T: Codable> {
    let key: String
    let defaultValue: T

    var wrappedValue: T {
        get {
            if let encodedValue = UserDefaults.standard.object(forKey: key) as? Data {
                let decoder = JSONDecoder()
                let decodedValue = try! decoder.decode(T.self, from: encodedValue)
                return decodedValue
            } else {
                return defaultValue
            }
        } set {
            let encoder = JSONEncoder()
            let encodedValue = try! encoder.encode(newValue)
            UserDefaults.standard.set(encodedValue, forKey: key)
        }
    }
}

But already having a property wrapper means that I can't use the @Published property wrapper from Combine. 但是已经有了属性包装器意味着我不能使用Combine的@Published属性包装器。 (Using two property wrappers on one variable doesn't sound like a good idea, and I haven't found a way to get that working.) (在一个变量上使用两个属性包装器听起来并不是一个好主意,而且我还没有找到一种使之工作的方法。)

I solved that problem by making a custom objectWillChange let constant and calling its .send(input:) method in willSet for every variable. 我通过使自定义objectWillChange let常量并在willSet中为每个变量调用其.send(input:)方法解决了该问题。

So this is my DataStore class: 这是我的DataStore类:

import SwiftUI
import Combine

final class DataStore: ObservableObject {
    let objectWillChange = PassthroughSubject<DataStore, Never>()

    @UserDefault(key: "the text", defaultValue: "Hello world!")
    var text: String {
        willSet {
            objectWillChange.send(self)
        }
    }
}

And this is my View: 这是我的观点:

struct StartView : View {
    @EnvironmentObject var dataStore: DataStore
    var body: some View {
        VStack {
            TextField("Enter text", text: $dataStore.text)
            Button("Reset text", action: {
                self.dataStore.text = "Hello World!"
            })
        }
    }
}

But somehow I really believe that there should be a more beautiful way than making a custom objectWillChange. 但是我真的以某种方式相信应该有一个比制作自定义objectWillChange更漂亮的方法。 Is there a way to make a single property wrapper that covers both the persisting and the "publishing"? 有没有一种方法可以制作一个同时包含持久性和“发布性”的属性包装器? Or should I do something completely different, to reach my goal? 还是我应该做一些完全不同的事情来实现自己的目标?

Thanks! 谢谢!

Based on the guessed implementation of Genetec Tech ( https://medium.com/genetec-tech/property-wrappers-in-swift-5-1-the-missing-published-implementation-1a466ebcf660 ) you could combine the two property wrappers into one @PublishedUserDefault. 基于Genetec Tech的猜测实现( https://medium.com/genetec-tech/property-wrappers-in-swift-5-1-the-missing-published-implementation-1a466ebcf660 ),可以将两个属性包装器组合在一起到一个@PublishedUserDefault中。

Example: 例:

This is the normal propertyWrapper for the UserDefaults 这是UserDefaults的常规propertyWrapper。

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

    var wrappedValue: T {
        get {
            UserDefaults.standard.value(forKey: key) as? T ?? defaultValue
        } set {
            UserDefaults.standard.set(newValue, forKey: key)
        }
    }
}

This viewModel also updates, when UserDefaults.set(_ ,forKey:) is called. 当调用UserDefaults.set(_,forKey :)时,此viewModel也将更新。

final class UserSettings: ObservableObject {
    let objectWillChange = ObservableObjectPublisher()

    @UserDefault(key: "text", defaultValue: "")
    var text: String

    private var notificationSubscription: AnyCancellable?

    init() {
        notificationSubscription = NotificationCenter.default.publisher(for: UserDefaults.didChangeNotification).sink { _ in
            self.objectWillChange.send()
        }
    }
}
private var cancellables = [String:AnyCancellable]()

extension Published {
    init(wrappedValue defaultValue: Value, key: String) {
        let value = UserDefaults.standard.object(forKey: key) as? Value ?? defaultValue
        self.init(initialValue: value)
        cancellables[key] = projectedValue.sink { val in
            UserDefaults.standard.set(val, forKey: key)
        }
    }
}

final class DataStore: ObservableObject {
    @Published(key: "theText")
    var text = "Hello world!"
}

Sample: https://youtu.be/TXdAg_YvBNE 范例: https//youtu.be/TXdAg_YvBNE

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM