仅在使用 Completion Handler 解决异步后更新视图

Update View Only After Aync Is Resolved with Completion Handler

我正在尝试更新我的视图,只有在解决异步调用之后。在下面的代码中,arrayOfTodos.items 在呈现 TodoListApp 之后稍微异步进入。我遇到的问题是,当 onAppear 运行时,self.asyncTodoList.items 总是 为空,因为它还没有从网络调用中接收到数组的值.我一直在试图弄清楚如何推迟 运行 onAppear 直到 Promise 解决之后,就像使用完成处理程序一样?并根据网络调用的结果,再修改视图。谢谢你的帮助!我坚持这个的时间比我承认的还要长!

    struct ContentView: View {
        
        @StateObject var arrayOfTodos = AsyncGetTodosNetworkCall()
        
        var body: some View {
            TodoListApp(asyncTodoList: arrayOfTodos)
        }
    }

    struct TodoListApp: View {
        
        @ObservedObject var asyncTodoList: AsyncGetTodosNetworkCall
        @State private var showPopUp: Bool = false
        
        var body: some View {
            NavigationView {
                
                ZStack {
                    
                    VStack {
                        
                        Text("Top Area")
                        Text("List Area")
                        
                    }
                    
                    if self.showPopUp == true {
                        VStack {
                            Text("THIS IS MY POPUP!")
                            Text("No Items Added Yet")
                        }.frame(width: 300, height: 400)
                    }
                    
                }.onAppear {
                    let arrayItems = self.asyncTodoList

                    if arrayItems.items.isEmpty {
                        self.showPopUp = true
                    }
                    
                    /*HERE! arrayItems.items.isEmpty is ALWAYS empty when onAppear 
                    runs since it's asynchronous.  What I'm trying to do is only
                    show the popup if the array is empty after the promise is
                    resolved.
                    What is happening is even if array resolved with multiple todos,
                    the popup is still showing because it was initially empty on
                    first run. */
                    
                }
                
            }
        }
    }

    class AsyncGetTodosNetworkCall: ObservableObject {
        
        @AppStorage(DBUser.userID) var currentUserId: String?
        private var REF_USERS = DB_BASE.collection(DBCOLLECTION.appUsers)
        @Published var items = [TodoItem]()
        
        func fetchTodos(toDetach: Bool) {
            
            guard let userID = currentUserId else {
                return
            }
            
            let userDoc = REF_USERS.document(String(userID))
                .collection(DBCOLLECTION.todos)
                .addSnapshotListener({ (querySnapshot, error) in
                    
                    guard let documents = querySnapshot?.documents else {
                        print("No Documents Found")
                        return
                    }
                    
                    self.items = documents.map { document -> TodoItem in
                        
                        let todoID = document.documentID
                        let todoName = document.get(ToDo.todoName) as? String ?? ""
                        let todoCompleted = document.get(Todo.todoCompleted) as? Bool ?? false
                        
                        return TodoItem(
                            id: todoID,
                            todoName: todoName,
                            todoCompleted: todoCompleted
                        )
                    }
                })
            if toDetach == true {
                userDoc.remove()
            }
        }
    }

在准备我的问题时,我找到了自己的答案。这是为了防止有人在路上 运行 遇到同样的问题。

    struct ContentView: View {

        @StateObject var arrayOfTodos = AsyncGetTodosNetworkCall()
        
        @State var hasNoTodos: Bool = false

        func getData() {
            self.arrayOfTodos.fetchTodos(toDetach: false) { noTodos in
                
                if noTodos {
                    self.hasNoTodos = true
                }
            }
        }

        func removeListeners() {
            self.arrayOfTodos.fetchTodos(toDetach: true)
        }

        var body: some View {

            TabView {
                TodoListApp(asyncTodoList: arrayOfTodos, hasNoTodos : self.$hasNoTodos)
            }.onAppear(perform: {
                self.getData()
            }).onDisappear(perform: {
                self.removeListeners()
            })
        }
    }

    struct TodoListApp: View {

        @ObservedObject var asyncTodoList: AsyncGetTodosNetworkCall
        @Binding var hasNoTodos: Bool
        @State private var hidePopUp: Bool = false

        var body: some View {
            NavigationView {

                ZStack {

                    VStack {

                        Text("Top Area")
                        
                        ScrollView {
                            LazyVStack {
                                
                                ForEach(asyncTodoList.items) { item in
                                    
                                    HStack {
                                        Text("\(item.name)")
                                        Spacer()
                                        Text("Value")
                                    }
                                    
                                }
                            }
                        }

                    }
                    
                    if self.hasNoTodos == true {
                        if self.hidePopUp == false {
                            
                            VStack {
                                Text("THIS IS MY POPUP!")
                                Text("No Items Added Yet")
                            }.frame(width: 300, height: 400)
                            
                        }
                    }

                }

            }
        }
    }
    class AsyncGetTodosNetworkCall: ObservableObject {

        @AppStorage(DBUser.userID) var currentUserId: String?
        private var REF_USERS = DB_BASE.collection(DBCOLLECTION.appUsers)
        @Published var items = [TodoItem]()

        func fetchTodos(toDetach: Bool, handler: @escaping (_ noTodos: Bool) -> ()) {

            guard let userID = currentUserId else {
                handler(true)
                return
            }

            let userDoc = REF_USERS.document(String(userID))
                .collection(DBCOLLECTION.todos)
                .addSnapshotListener({ (querySnapshot, error) in

                    guard let documents = querySnapshot?.documents else {
                        print("No Documents Found")
                        handler(true)
                        return
                    }

                    self.items = documents.map { document -> TodoItem in

                        let todoID = document.documentID
                        let todoName = document.get(ToDo.todoName) as? String ?? ""
                        let todoCompleted = document.get(Todo.todoCompleted) as? Bool ?? false

                        return TodoItem(
                            id: todoID,
                            todoName: todoName,
                            todoCompleted: todoCompleted
                        )
                    }
                    handler(false)
                })
            if toDetach == true {
                userDoc.remove()
            }
        }
    }