【问题标题】:SwiftUI @Published variable not updatingSwiftUI @Published 变量未更新
【发布时间】:2021-09-07 22:20:44
【问题描述】:

我有一个带有名为 userData 的 @Published 变量的类。当在此类中调用函数 fbLogic.getUserData() 时,它会使用从对 Firestore 数据库的调用中填充的数据更新 userData 数组。

问题是这个发布的变量在访问它的视图中没有更新。或者更重要的是,这些函数不会等到 Firestore 调用完成后才访问变量。所以 BarGraphView() 是用空的 reports[] 数组调用的,并且这个数组不会被 convertToReports() 调用更新,因为 convertToReports() 是用一个空的 userData 数组调用的,因为 fbLogic.getUserData() 还没有完成检索Firebase 数据。希望代码能更好地解释它。

这是引用数组的视图。

struct StatsView: View {
    let ID: String;
    @EnvironmentObject var statsController: StatsDataController
    @State var reports: [Report] = [Report]();
    @Binding var fbLogic: FirebaseLogic
    var body: some View {
 
       
        BarGraphView(selection: $selection, reports: $reports, originalReports: $originalReports).environmentObject(statsController).onAppear{
            fbLogic.userData = fbLogic.getUserData(uid: ID); //supposed to update the userData @Published var
            

            //These are called before fbLogic.userData has even been updated !!!
            self.reports = statsController.convertToReports(users: fbLogic.userData);
            print("fblogic data ", fbLogic.userData, " ", ID);};

    }

此视图在对 .getUserData() 的调用完成之前打印出“fb logic data []”,因此对 convertToReports() 的调用发生在一个空数组,而不是填充数组。

请问,我如何让该视图识别 fbLogic.userData @Pubished 数组已更新并调用 convertToReports(),并使用 POPULATED 数组重新绘制 BarGraphView。

这是 fbLogic 类的一部分,因此您可以理解对 firebase 的调用:

class FirebaseLogic: ObservableObject {
@Published var userData = [UserData]()
    
   
 func getUserData(uid: String) -> [UserData]{
            let db = Firestore.firestore()
            userData = [UserData]()
            let auth = Auth.auth();
            let currentUser = (auth.currentUser?.uid)!
            
                let data = db.collection("UserData").document(currentUser).collection("Data")    .getDocuments { [self] (snapshot, error) in
                    guard let snapshot = snapshot, error == nil else {
                     return
                   }
                   print("Number of documents fb logic get user data: \(snapshot.documents.count ?? -1)")
                   snapshot.documents.forEach({ (documentSnapshot) in
                       
                       
                    let documentData = documentSnapshot.data()
                    //CONVERT TO userObject here (removed for simplicity)
                   
                    userData.append(userObject)

                   })
                   
                 }
                
            return userData
            
        }

【问题讨论】:

  • 你可以试试“@ObservedObject var fbLogic: FirebaseLogic”而不是“@Binding var fbLogic: FirebaseLogic”
  • @workingdog 不幸的是这并没有改变行为:(
  • UserData 是结构体还是类?

标签: firebase google-cloud-firestore swiftui


【解决方案1】:

尝试使用

@ObservedObject var fbLogic: FirebaseLogic

在统计视图中。当您将数据从父视图传递到此视图时,请确保将 @ObservedObject 类型传递给 StatsView。

【讨论】:

  • 我已经在 StatsView 和实例化 StatsView 的父级中将 fbLogic 更改为 @ObservedObject。虽然行为仍然相同 :( 我通过执行 @State var fbLogic = FirebaseLogic() 并向下传递给子视图来在应用程序的顶层实例化 fbLogic。这是问题吗?
  • 是的,尝试使用@StateObject var fbLogic = FirebaseLogic()
  • @workingdog 如果将其更改为 StateObject,那么我实际上什至无法登录应用程序,可能是因为 fbLogic 变量中的更改会强制在应用程序的最顶层刷新并设置我返回未经身份验证。你知道我怎样才能让 fbLogic 只为儿童观察吗?我不想在应用程序根级别刷新,只在子级别刷新,例如 StatsView()。如果有意义的话,我希望发布的变量仅针对 StatsView 和 StatsView 的子项进行更新
  • 要观察一个对象,你需要用 ObservedObject 或者最好是 StateObject 来实例化它。我不知道你有时如何观察一个物体,但在其他时候却不是。在你的情况下,也许你可以在 StatsView 中声明“@StateObject var fbLogic = FirebaseLogic()”,不要从父级传递它。
【解决方案2】:

这不起作用的原因是对getDocuments() 的调用是异步的。所以当你返回映射文档的数组时(考虑使用 Codable),它仍然是空的。

这是您的代码的更新版本,它将映射的文档分配给已发布的属性。

class FirebaseLogic: ObservableObject {
  @Published var userData = [UserData]()
  func getUserData(uid: String) -> [UserData] {
    let db = Firestore.firestore()
    let localUserData = [UserData]()
    let auth = Auth.auth();
    let currentUser = (auth.currentUser?.uid)! // (1)
    db.collection("UserData").document(currentUser).collection("Data").getDocuments { [self] (snapshot, error) in
      guard let snapshot = snapshot, error == nil else {
        return
      }
      print("Number of documents fb logic get user data: \(snapshot.documents.count ?? -1)")
      snapshot.documents.forEach { documentSnapshot in
        let documentData = documentSnapshot.data()
        // CONVERT TO userObject here (removed for simplicity)
        localUserData.append(userObject)
      }
      self.userData = localUserData
    }
  }
}

您可以通过使用.map() 来转换您的文档来进一步改进此代码。看到我的article about mapping Firestore documents to/from Swift中的这个code snippet that is part

listenerRegistration = db.collection("programming-languages") 
  .addSnapshotListener { [weak self] (querySnapshot, error) in
    guard let documents = querySnapshot?.documents else {
      self?.errorMessage = "No documents in 'programming-languages' collection"
      return
    }
          
    self?.programmingLanguages = documents.compactMap { queryDocumentSnapshot in
      let result = Result { try queryDocumentSnapshot.data(as: ProgrammingLanguage.self) }
    ...

【讨论】:

  • 嗨彼得,感谢您的回答!我正在尝试执行您目前建议的第一个选项,但编译器抱怨self.userData = userData 行,并出现错误“为自身分配属性”。你知道如何解决这个问题吗?
  • 我更新了代码以使用局部变量。话虽如此,我建议使用我指出的compactMap 方法。它将文档映射保存在闭包中,并在完成后将映射的文档分配给发布的属性。另外,它会忽略nil 项目。
猜你喜欢
  • 2020-10-18
  • 2021-10-14
  • 2020-04-30
  • 2022-01-04
  • 2020-11-12
  • 1970-01-01
  • 1970-01-01
  • 2020-10-21
  • 2023-04-08
相关资源
最近更新 更多