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

点击其他视图按钮后,调用Firestore的onAppear视图为空问题

问题描述

我在另一个视图点击按钮后加载当前RewardsView,使用模拟数据时视图显示正常,但切换为调用Firestore数据库数据后,视图无内容显示。我学习SwiftUI仅数月,项目其他部分用相同方式调用数据库是正常的,附上代码示例,怀疑是对SwiftUI视图运行时构建逻辑理解有误,寻求帮助。

代码示例
import SwiftUI
import FirebaseFirestore
import FirebaseFirestoreSwift

struct RewardsView: View {
    
// Dummy Data
//    var rewardItems: [Reward] = [
//        .init(RewardText: "10% Off Next Haircut", RewardRedeemed: false, rewardPhoto: "crown_kings_main_photo", costOfReward: 10),
//        .init(RewardText: "10% Off Extra Service", RewardRedeemed: true, rewardPhoto: "crown_kings_main_photo", costOfReward: 8),
//        .init(RewardText: "20% Next Haircut", RewardRedeemed: false, rewardPhoto: "crown_kings_main_photo", costOfReward: 15),
//        .init(RewardText: "30% off Next Haircut", RewardRedeemed: false, rewardPhoto: "crown_kings_main_photo", costOfReward: 20),
//        .init(RewardText: "25% Off Next Haircut", RewardRedeemed: true, rewardPhoto: "crown_kings_main_photo", costOfReward: 18),
//        .init(RewardText: "45% Off Next Haircut", RewardRedeemed: true, rewardPhoto: "crown_kings_main_photo", costOfReward: 30)
//    ]
    
    

    @EnvironmentObject private var rewardsViewModel: RewardViewModel
    @EnvironmentObject private var loyaltyViewModel: LoyaltyViewModel
    
    @State var rewards: [Reward] = []
    
    func getRewards() async throws -> [Reward]{
        let db = Firestore.firestore()
        let snapshot = try await db.collection("redeemRewards").getDocuments()
        let rewards = snapshot.documents.compactMap{try? $0.data(as: Reward.self)}
        
        return rewards
    }
   
    
    
    var body: some View {
        NavigationStack{
            ScrollView {
                LazyVGrid (columns: [
                    GridItem(.flexible()),
                    GridItem(.flexible()),
                ], spacing: 16, content:{
                    ForEach(rewardsViewModel.rewards, id: \.self) { reward in
                        RewardsCardView(rewardPhoto:reward.rewardPhoto, rewardText: reward.rewardText, rewardRedeemed: reward.rewardRedeemed, costOfReward: reward.costOfReward)
                            .environmentObject(rewardsViewModel)
                            .environmentObject(loyaltyViewModel)
                        
                    }
                })
            }
            .onAppear {
                Task{
                    do {
                        let newRewards = try await getRewards()
                        rewards.append(contentsOf: newRewards)
                    } catch {
                        print("Error Fetching rewards: \(error.localizedDescription)")
                    }
                }
            }
            .navigationTitle("Rewards")
        }
    }
}

//#Preview {
//    RewardsView()
//}

struct Reward: Identifiable, Codable,Hashable {
    @DocumentID var id: String?
    let rewardText: String
    let rewardRedeemed: Bool
    let rewardPhoto: String
    let costOfReward: CGFloat
    let timestamp: Timestamp
    let userId: String
    let remainingPoints: CGFloat
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
    
    static func == (lhs: Reward, rhs: Reward) -> Bool {
        lhs.id == rhs.id
    }
}
问题分析与修复方案

核心问题1:数据源不匹配

你在LazyVGrid中遍历的是rewardsViewModel.rewards,但onAppear里把Firestore获取到的数据存在了本地@State var rewards数组中,完全没有更新ViewModel的数据源,这是视图无内容的直接原因。

核心问题2:解析错误无反馈

compactMap{try? $0.data(as: Reward.self)}使用try?会把解析错误吞掉,导致即使Firestore返回了数据,只要解析失败就会返回空数组,你却看不到任何错误提示。

其他潜在问题

  • onAppear在NavigationStack的缓存机制下可能不会触发(比如从其他视图返回时),不如.task修饰符可靠。
  • 需确认Firestore文档字段名和Reward结构体字段完全一致(比如结构体用rewardText,Firestore里不能写成RewardText),否则解析会失败。
  • 需确认RewardViewModel中的rewards是@Published属性,否则更新数组不会触发视图刷新。

修复步骤

  1. 修正数据源更新逻辑
    将获取到的数据更新到ViewModel的rewards数组:

    .onAppear {
        Task{
            do {
                let newRewards = try await getRewards()
                // 替换为更新ViewModel的数组
                rewardsViewModel.rewards = newRewards
            } catch {
                print("获取奖励失败: \(error.localizedDescription)")
            }
        }
    }
    
  2. 强化错误排查
    把try?改成try,让解析错误能被捕获并打印:

    func getRewards() async throws -> [Reward]{
        let db = Firestore.firestore()
        let snapshot = try await db.collection("redeemRewards").getDocuments()
        // 用try替代try?,解析失败会抛出明确错误
        let rewards = try snapshot.documents.map{try $0.data(as: Reward.self)}
        return rewards
    }
    
  3. 替换onAppear为task
    使用.task替代.onAppear,确保异步任务在视图出现时可靠执行:

    .task {
        do {
            let newRewards = try await getRewards()
            rewardsViewModel.rewards = newRewards
        } catch {
            print("获取奖励失败: \(error.localizedDescription)")
        }
    }
    
  4. 检查ViewModel属性
    确保RewardViewModel中的rewards是@Published:

    class RewardViewModel: ObservableObject {
        @Published var rewards: [Reward] = []
        // 其他逻辑
    }
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 23:43:19