如何观察UserDefaults中的更改?

时间:2020-07-03 10:07:58

标签: ios swift swiftui

我的视图中有一个@ObservedObject

struct HomeView: View {

    @ObservedObject var station = Station()

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

根据String中的Station.status更新文本:

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

但是,我需要在status中更改AppDelegate的值,因为那是我收到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
}

但是,如果我更改status中的UserDefaults AppDelegate值-在我看来它不会更新。

@ObservedObject发生变化时,如何通知我的status

编辑:忘记提及在上述示例中使用了SwiftUI的2.0 beta版本。

2 个答案:

答案 0 :(得分:2)

我建议您改用环境对象或根据需要将两者结合使用。环境对象基本上是全局状态对象。因此,如果您更改环境对象的已发布属性,它将反映您的视图。要进行设置,您需要通过 SceneDelegate 将对象传递到初始视图,然后可以使用整个视图层次结构中的状态。这也是跨非常远的同级视图(或者如果您有更复杂的场景)传递数据的方法。

简单示例

在您的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()
    }
}

全局状态应符合 ObservableObject 。您应该将全局变量作为 @Published 放置在此处。

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

如何发布变量的示例,该变量与SceneDelegate中已显示的示例无关

这就是您可以如何在视图内部处理全局状态的方法。您需要使用 @EnvironmentObject 包装器将其注入,如下所示:

struct ContentView: View {
    
    @EnvironmentObject var globalState: GlobalState

    var body: some View {
        Text("Hello World")
    }
}

现在,根据您的情况,您还希望使用AppDelegate中的状态。为此,我建议您将AppDelegate中的全局状态变量安全保存,并在传递到初始视图之前从SceneDelegate中的全局状态变量中进行访问。为此,您应该在AppDelegate中添加以下内容:

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

现在,您可以返回SceneDelegate并执行以下操作,而不是直接初始化GlobalState:

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

答案 1 :(得分:1)

这是可能的解决方法

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

注意:SwiftUI 2.0 允许您直接通过UserDefaults在视图中使用/观察AppStorage,因此,如果仅在视图中需要该状态,则可以使用< / p>

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