简体   繁体   English

如何观察 UserDefaults 的变化?

[英]How to observe changes in UserDefaults?

I have an @ObservedObject in my View:我的视图中有一个@ObservedObject

struct HomeView: View {

    @ObservedObject var station = Station()

    var body: some View {
        Text(self.station.status)
    }
 

which updates text based on a String from Station.status :它根据来自Station.statusString更新文本:

class Station: ObservableObject {
    @Published var status: String = UserDefaults.standard.string(forKey: "status") ?? "OFFLINE" {
        didSet {
            UserDefaults.standard.set(status, forKey: "status")
        }
    }      

However, I need to change the value of status in my AppDelegate , because that is where I receive my Firebase Cloud Messages :但是,我需要在AppDelegate中更改status的值,因为这是我收到Firebase Cloud Messages的地方:

func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
                 fetchCompletionHandler completionHandler: @escaping (UIBackgroundFetchResult) -> Void) {
  // If you are receiving a notification message while your app is in the background,
  // this callback will not be fired till the user taps on the notification launching the application.

  // Print full message.
  let rawType = userInfo["type"]

  // CHANGE VALUE OF status HERE
}

But if I change the status UserDefaults value in AppDelegate - it won't update in my view.但是,如果我更改AppDelegate中的status UserDefaults值 - 在我看来它不会更新。

How can my @ObservedObject in my view be notified when status changes?status更改时,如何通知我视图中的@ObservedObject

EDIT: Forgot to mention that the 2.0 beta version of SwiftUI is used in the said example.编辑:忘记提及在上述示例中使用了 SwiftUI 的 2.0 beta 版本。

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注意:SwiftUI 2.0允许您直接通过AppStorage在视图中使用/观察UserDefaults ,因此如果您只需要在视图中显示该状态,则可以使用

struct SomeView: View {
    @AppStorage("status") var status: String = "OFFLINE"
    ...

I would suggest you to use environment object instead or a combination of both of them if required.如果需要,我建议您使用环境 object或两者的组合。 Environment objects are basically a global state objects.环境对象基本上是一个全局 state对象。 Thus if you change a published property of your environment object it will reflect your view.因此,如果您更改环境 object 的已发布属性,它将反映您的视图。 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 .要设置它,您需要通过SceneDelegate将 object 传递给您的初始视图,并且您可以在整个视图层次结构中使用 state。 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:在您的 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 .全局 state 应符合ObservableObject You should put your global variables in there as @Published .您应该将全局变量作为@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如何发布变量的示例,与 SceneDelegate 中已显示的示例无关

This is then how you can work with your global state inside your view.这就是您如何在视图中使用全局 state 的方式。 You need to inject it with the @EnvironmentObject wrapper like this:您需要使用@EnvironmentObject包装器注入它,如下所示:

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.现在,在您的情况下,您还想在 AppDelegate 中使用 state。 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.为了做到这一点,我建议您在 AppDelegate 中保护全局 state 变量,并在您的 SceneDelegate 中从那里访问它,然后再传递到初始视图。 To achieve this you should add the following in your AppDelegate:为此,您应该在 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:现在您可以 go 回到您的 SceneDelegate 并执行以下操作,而不是直接初始化 GlobalState:

let contentView = ContentView().environmentObject(AppDelegate.shared().globalState)

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

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