簡體   English   中英

如何觀察 UserDefaults 的變化?

[英]How to observe changes in UserDefaults?

我的視圖中有一個@ObservedObject

struct HomeView: View {

    @ObservedObject var station = Station()

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

它根據來自Station.statusString更新文本:

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

但是,我需要在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
}

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

status更改時,如何通知我視圖中的@ObservedObject

編輯:忘記提及在上述示例中使用了 SwiftUI 的 2.0 beta 版本。

這是可能的解決方案

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允許您直接通過AppStorage在視圖中使用/觀察UserDefaults ,因此如果您只需要在視圖中顯示該狀態,則可以使用

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

如果需要,我建議您使用環境 object或兩者的組合。 環境對象基本上是一個全局 state對象。 因此,如果您更改環境 object 的已發布屬性,它將反映您的視圖。 要設置它,您需要通過SceneDelegate將 object 傳遞給您的初始視圖,並且您可以在整個視圖層次結構中使用 state。 這也是跨非常遠的兄弟視圖(或者如果您有更復雜的場景)傳遞數據的方式。

簡單示例

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

全局 state 應符合ObservableObject 您應該將全局變量作為@Published放在那里。

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

如何發布變量的示例,與 SceneDelegate 中已顯示的示例無關

這就是您如何在視圖中使用全局 state 的方式。 您需要使用@EnvironmentObject包裝器注入它,如下所示:

struct ContentView: View {
    
    @EnvironmentObject var globalState: GlobalState

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

現在,在您的情況下,您還想在 AppDelegate 中使用 state。 為了做到這一點,我建議您在 AppDelegate 中保護全局 state 變量,並在您的 SceneDelegate 中從那里訪問它,然后再傳遞到初始視圖。 為此,您應該在 AppDelegate 中添加以下內容:

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

現在您可以 go 回到您的 SceneDelegate 並執行以下操作,而不是直接初始化 GlobalState:

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

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM