5 votes

SwiftUI : Comment persister une variable @EnvironmentObject ?

Je veux qu'une variable soit un EnvironmentObject et je veux aussi qu'elle soit persistante, de sorte qu'elle soit la même à chaque fois que je relance mon application.

Pour ce faire, j'ai déjà créé le propertyWrapper suivant :

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

Mais le fait d'avoir déjà un wrapper de propriété signifie que je ne peux pas utiliser le wrapper de propriété @Published de Combine (utiliser deux wrappers de propriété sur une variable ne semble pas être une bonne idée, et je n'ai pas trouvé le moyen de faire fonctionner cela).

J'ai résolu ce problème en créant un objectWillChange et en appelant son .send(input:) dans willSet pour chaque variable.

Voici donc ma classe 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)
        }
    }
}

Et voici mon point de vue :

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!"
            })
        }
    }
}

Mais je crois vraiment qu'il devrait y avoir un moyen plus beau que de faire un objectWillChange personnalisé. Existe-t-il un moyen de faire un wrapper de propriété unique qui couvre à la fois la persistance et la "publication" ? Ou dois-je faire quelque chose de complètement différent pour atteindre mon but ?

Gracias.

3voto

mydefs Points 145

Sur la base de la mise en œuvre anticipée de Genetec Tech ( https://medium.com/genetec-tech/property-wrappers-in-swift-5-1-the-missing-published-implementation-1a466ebcf660 ), vous pourriez combiner les deux enveloppes de propriété en une seule @PublishedUserDefault.

Ejemplo:

Il s'agit de l'habillage normal de la propriété pour les UserDefaults

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

Ce modèle de vue est également mis à jour lorsque UserDefaults.set(_ ,forKey :) est appelé.

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

0voto

Victor Kushnerov Points 2520
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!"
}

Échantillon : https://youtu.be/TXdAg_YvBNE

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X