How to observe changes in UserDefaults?

后端 未结 2 2049
日久生厌
日久生厌 2020-12-21 18:53

I have an @ObservedObject in my View:

struct HomeView: View {

    @ObservedObject var station = Station()

    var body: some View {
        Tex         


        
相关标签:
2条回答
  • 2020-12-21 19:18

    I would suggest you to use environment object instead or a combination of both of them if required. Environment objects are basically a global state objects. Thus if you change a published property of your environment object it will reflect your view. To set it up you need to pass the object to your initial view through SceneDelegate and you can work with the state in your whole view hierarchy. This is also the way to pass data across very distant sibling views (or if you have more complex scenario).

    Simple Example

    In your SceneDelegate.swift:

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
            
        let contentView = ContentView().environmentObject(GlobalState())
    
        if let windowScene = scene as? UIWindowScene {
            let window = UIWindow(windowScene: windowScene)
            window.rootViewController = UIHostingController(rootView: contentView)
            self.window = window
            window.makeKeyAndVisible()
        }
    }
    

    The global state should conform ObservableObject. You should put your global variables in there as @Published.

    class GlobalState: ObservableObject {
        @Published var isLoggedIn: Bool
        
        init(isLoggedIn : Bool) {
            self.isLoggedIn = isLoggedIn
        }
    }
    

    Example of how you publish a variable, not relevant to the already shown example in SceneDelegate

    This is then how you can work with your global state inside your view. You need to inject it with the @EnvironmentObject wrapper like this:

    struct ContentView: View {
        
        @EnvironmentObject var globalState: GlobalState
    
        var body: some View {
            Text("Hello World")
        }
    }
    

    Now in your case you want to also work with the state in AppDelegate. In order to do this I would suggest you safe the global state variable in your AppDelegate and access it from there in your SceneDelegate before passing to the initial view. To achieve this you should add the following in your AppDelegate:

    var globalState : GlobalState!
        
    static func shared() -> AppDelegate {
        return UIApplication.shared.delegate as! AppDelegate
    }
    

    Now you can go back to your SceneDelegate and do the following instead of initialising GlobalState directly:

    let contentView = ContentView().environmentObject(AppDelegate.shared().globalState)
    
    0 讨论(0)
  • 2020-12-21 19:30

    Here is possible solution

    import Combine
    
    // define key for observing
    extension UserDefaults {
        @objc dynamic var status: String {
            get { string(forKey: "status") ?? "OFFLINE" }
            set { setValue(newValue, forKey: "status") }
        }
    }
    
    class Station: ObservableObject {
        @Published var status: String = UserDefaults.standard.status {
            didSet {
                UserDefaults.standard.status = status
            }
        }
    
        private var cancelable: AnyCancellable?
        init() {
            cancelable = UserDefaults.standard.publisher(for: \.status)
                .sink(receiveValue: { [weak self] newValue in
                    guard let self = self else { return }
                    if newValue != self.status { // avoid cycling !!
                        self.status = newValue
                    }
                })
        }
    }
    

    Note: SwiftUI 2.0 allows you to use/observe UserDefaults in view directly via AppStorage, so if you need that status only in view, you can just use

    struct SomeView: View {
        @AppStorage("status") var status: String = "OFFLINE"
        ...
    
    0 讨论(0)
提交回复
热议问题