You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI如何等待异步请求完成后再执行onAppear逻辑更新视图

核心问题说明

  1. 现有代码没有主动调用fetchTodos方法,网络请求根本不会执行
  2. onAppear在视图渲染完成就立即触发,不会等待异步请求返回结果,此时读取的items永远是初始化的空数组

解决方案

我们可以通过增加加载状态标识+监听状态变化的方式,实现只有异步请求完成后才判断是否显示弹窗,修改步骤如下:

步骤1:修改数据请求类,增加加载状态

class AsyncGetTodosNetworkCall: ObservableObject {
    @AppStorage(DBUser.userID) var currentUserId: String?
    private var REF_USERS = DB_BASE.collection(DBCOLLECTION.appUsers)
    @Published var items = [TodoItem]()
    // 新增加载状态标记,避免初始空值误判
    @Published var isDataLoaded = false

    func fetchTodos(toDetach: Bool) {
        guard let userID = currentUserId else {
            // 无用户ID的场景也标记加载完成
            isDataLoaded = true
            return
        }
        let userDoc = REF_USERS.document(String(userID))
            .collection(DBCOLLECTION.todos)
            .addSnapshotListener({ [weak self] (querySnapshot, error) in
                guard let self = self else { return }
                // 回调执行完成后统一标记加载完成
                defer { self.isDataLoaded = true }

                guard let documents = querySnapshot?.documents else {
                    print("No Documents Found")
                    self.items = []
                    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()
        }
    }
}

步骤2:修改ContentView,主动触发网络请求

struct ContentView: View {
    @StateObject var arrayOfTodos = AsyncGetTodosNetworkCall()
    var body: some View {
        TodoListApp(asyncTodoList: arrayOfTodos)
            .onAppear {
                // 触发网络请求,根据业务需要传入toDetach参数
                arrayOfTodos.fetchTodos(toDetach: false)
            }
    }
}

步骤3:修改TodoListApp,监听加载完成状态后再判断弹窗显示逻辑

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 showPopUp {
                    VStack {
                        Text("THIS IS MY POPUP!")
                        Text("No Items Added Yet")
                    }.frame(width: 300, height: 400)
                }
            }
            // 监听加载完成状态,只有加载完成后才判断是否显示弹窗
            .onChange(of: asyncTodoList.isDataLoaded) { newValue in
                guard newValue else { return }
                showPopUp = asyncTodoList.items.isEmpty
            }
            // 可选:如果需要后续数据库实时更新后同步调整弹窗状态,增加对items的监听
            .onChange(of: asyncTodoList.items) { newValue in
                guard asyncTodoList.isDataLoaded else { return }
                showPopUp = newValue.isEmpty
            }
        }
    }
}

逻辑说明

  • 通过isDataLoaded标记过滤了请求未完成时的初始空数组场景,避免误判
  • 如果不需要实时监听数据库变化,仅需要单次请求,可以把addSnapshotListener替换为getDocuments接口,逻辑更简洁

内容的提问来源于stack exchange,提问作者Clay_F

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 17:24:02