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

SwiftUI跨视图共享Firebase请求数据失败问题求助

问题分析与解决方案

核心问题

你遇到的问题根源是UpperView和LowerView各自初始化了独立的MergeViewModel实例。两个视图持有完全不同的ViewModel对象,当UpperView调用fetchAllClients()获取数据后,只有它自己持有的ViewModel实例更新了clients数组,LowerView的ViewModel实例完全没有数据,自然无法展示内容。

另外UpperView里的按钮点击逻辑还有一个问题:fetchAllClients()是异步操作(Firebase获取数据是网络请求),你在调用后立即取viewModel.clients.count,此时数据还没返回,所以numberOfClients会一直是0。


解决方案步骤

  1. 在父视图共享ViewModel实例:在HomeView中创建MergeViewModel的唯一实例,通过参数传递给UpperView和LowerView,确保两个子视图使用同一个ViewModel。
  2. 移除子视图的ViewModel初始化:UpperView和LowerView不再自己创建ViewModel,而是通过外部传入。
  3. 修正异步数据的读取逻辑:利用@Published的特性,直接绑定ViewModel的clients属性,或者用onReceive监听数据变化,避免异步操作后立即读取数据。

修改后的完整代码

1. MergeViewModel(无需修改)

import Foundation

class MergeViewModel: ObservableObject {
    @Published var clients: [Client] = [Client]()
    
    func fetchAllClients() {
        COLLECTION_CLIENTS.getDocuments { querySnapshot, error in
            if let error = error {
                print(error.localizedDescription)
                return
            }
            guard let documents = querySnapshot?.documents else { return }
            self.clients = documents.compactMap({ try? $0.data(as: Client.self)})
            print(self.clients.count)
        }
    }
}

2. HomeView(添加ViewModel实例并传递给子视图)

import SwiftUI

struct HomeView: View {
    // iOS14+ 使用@StateObject持有ObservableObject实例
    @StateObject private var mergeViewModel = MergeViewModel()
    
    var body: some View {
        NavigationView {
            VStack {
                // 将共享的ViewModel传递给UpperView
                UpperView(viewModel: mergeViewModel)
                // 同样传递给LowerView
                LowerView(viewModel: mergeViewModel)
                Spacer()
            }
            .navigationTitle("")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .principal) {
                    HStack {
                        Image("logo_silueta")
                            .resizable()
                            .scaledToFit()
                            .frame(width: 30)
                        Text("TheJump")
                            .font(.subheadline)
                            .foregroundColor(.gray.opacity(0.8))
                    }
                }
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button(action: {
                        AuthViewModel.shared.signOut()
                    }, label: {
                        Text("logout")
                    })
                }
            }
        }
    }
}

3. UpperView(接收外部ViewModel,修正按钮逻辑)

import SwiftUI

struct UpperView: View {
    // 接收父视图传递的ViewModel实例
    @ObservedObject var viewModel: MergeViewModel
    @State var buttonPressed: Int = 0
    
    var body: some View {
        ZStack {
            Color(.red)
            VStack{
                Text("This is UPPER VIEW ")
                    .foregroundColor(.white)
                // 直接使用ViewModel的clients.count,数据更新时自动刷新
                Text("We have \(viewModel.clients.count) of clients!")
                Text("Button pressed \(buttonPressed)")
                
                Button(action: {
                    viewModel.fetchAllClients()
                    buttonPressed += 1
                }, label: {
                    Text("Press")
                        .frame(width: 100, height: 50)
                        .background(Color.white.opacity(0.50))
                        .cornerRadius(10)
                })
            }
        }.ignoresSafeArea()
    }
}

struct UpperView_Previews: PreviewProvider {
    static var previews: some View {
        UpperView(viewModel: MergeViewModel())
    }
}

4. LowerView(接收外部ViewModel)

import SwiftUI

struct LowerView: View {
    // 接收父视图传递的ViewModel实例
    @ObservedObject var viewModel: MergeViewModel
    
    var body: some View {
        VStack {
            Text("This is LOWER VIEW")
                .foregroundColor(.black)
            Text("\(viewModel.clients.count)")
                .foregroundColor(.black)
            List(viewModel.clients) { client in
                Text(client.clientName)
                    .foregroundColor(.black)
            }
        }
    }
}

struct LowerView_Previews: PreviewProvider {
    static var previews: some View {
        LowerView(viewModel: MergeViewModel())
    }
}

关键说明

  • @StateObject vs @ObservedObject:在父视图中用@StateObject创建ViewModel实例,确保它的生命周期和视图一致;子视图中用@ObservedObject接收实例,监听其@Published属性的变化。
  • 异步数据处理:因为Firebase的getDocuments是异步的,我们不需要手动更新numberOfClients,只要绑定viewModel.clients.count,当ViewModel的clients更新时,视图会自动刷新显示最新的数量。
  • 数据共享:通过父视图传递同一个ViewModel实例,实现了UpperView和LowerView的数据同步,当UpperView触发数据获取后,LowerView会自动接收到更新并展示数据。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.10 16:35:21