SwiftUI跨视图共享Firebase请求数据失败问题求助
问题分析与解决方案
核心问题
你遇到的问题根源是UpperView和LowerView各自初始化了独立的MergeViewModel实例。两个视图持有完全不同的ViewModel对象,当UpperView调用fetchAllClients()获取数据后,只有它自己持有的ViewModel实例更新了clients数组,LowerView的ViewModel实例完全没有数据,自然无法展示内容。
另外UpperView里的按钮点击逻辑还有一个问题:fetchAllClients()是异步操作(Firebase获取数据是网络请求),你在调用后立即取viewModel.clients.count,此时数据还没返回,所以numberOfClients会一直是0。
解决方案步骤
- 在父视图共享ViewModel实例:在HomeView中创建MergeViewModel的唯一实例,通过参数传递给UpperView和LowerView,确保两个子视图使用同一个ViewModel。
- 移除子视图的ViewModel初始化:UpperView和LowerView不再自己创建ViewModel,而是通过外部传入。
- 修正异步数据的读取逻辑:利用@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
相关产品推荐
相关产品推荐

