【问题标题】:How to observe changes in UserDefaults?如何观察 UserDefaults 的变化?
【发布时间】:2023-03-04 23:56:01
【问题描述】:

我的视图中有一个@ObservedObject:

struct HomeView: View {

    @ObservedObject var station = Station()

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

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

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 版本。

【问题讨论】:

    标签: ios swift swiftui


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

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

    【讨论】:

    • @AppStorage 看起来正是我所需要的——我会试试看。希望当我更改AppDelegate 中的status UserDefault 值时视图会更新。谢谢!
    【解决方案2】:

    如果需要,我建议您改用 环境对象 或两者结合使用。环境对象基本上是一个全局状态对象。因此,如果您更改环境对象的 published 属性,它将反映您的视图。要设置它,您需要通过 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)
    

    【讨论】:

    猜你喜欢
    • 2017-11-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-07-03
    • 1970-01-01
    • 2022-12-15
    相关资源
    最近更新 更多