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

SwiftUI视图无法更新Firestore数据问题求助

问题:SwiftUI从Firestore获取数据无法在视图中展示

我正在开发SwiftUI应用,通过AdminPostViewModel从Firestore获取帖子数据,打印显示文档数量正确,但posts数组实际为空(遍历打印无输出),导致HomeView的底部sheet(或ScrollView)中无法展示帖子。静态数据能正常显示,排除视图渲染问题,问题出在Firestore数据解析环节。


相关代码

AdminPostViewModel.swift

import Foundation
import FirebaseFirestore
import Firebase
import SwiftUI
import Combine

class AdminPostViewModel: ObservableObject {
    @Published var posts = [AdminPost]()
    private var db = Firestore.firestore()
    var cancellables = Set<AnyCancellable>()

    func fetchData() {
        db.collection("adminsPosts").addSnapshotListener { querySnapshot, error in
            if let error = error {
                print("Error fetching documents: \(error)")
                return
            }
            guard let documents = querySnapshot?.documents else {
                print("No documents in 'adminsPosts' collection")
                return
            }
            self.posts = documents.compactMap { queryDocumentSnapshot -> AdminPost? in
                try? queryDocumentSnapshot.data(as: AdminPost.self)
            }
            print("Fetched \(documents.count) posts.") // 打印正常
            self.posts.forEach { post in
                print("Post titlle: \(post.title)") // 无输出
            }
        }
    }
}

AdminPost.swift

struct AdminPost: Codable, Identifiable {
    @DocumentID var id: String?
    var title: String
    var body: String
    var tags: String
    var imageUrl: String 
    var timestamp: Date
    var isTrending: Bool 
    var views: InT // 拼写错误
    var numberOfComments: Int 
    var numberOfReactions: Int
    var isFeatured: Bool 
}

HomeView.swift

struct HomeView: View {
    @StateObject private var viewModel = AdminPostViewModel()
    @State private var bottomSheetPosition: BottomSheetPosition = .relative(0.4) // 补全缺失的状态定义

    var body: some View {
        // 补全基础视图(示例用Color作为根视图)
        Color.white
            .bottomSheet(bottomSheetPosition: self.$bottomSheetPosition, switchablePositions: [
                .relative(0.4),
                .relativeTop(0.975)
            ], headerContent: {
                Text("The Latest")
                    .padding(.leading,13)
                    .font(.system(size: 22))
                    .fontWeight(.bold)
            }) {
                ForEach(viewModel.posts) { post in 
                     NewsContentView(post: post)
                         .padding()
                 }
            }
            .onAppear {
                viewModel.fetchData()
            }
    }
}

NewsContentView.swift

struct NewsContentView: View {
    var post: AdminPost

    var body: some View {
        var body: some View { // 重复定义body
        VStack(alignment: .leading, spacing: 5) {
            HStack {
                VStack(alignment: .leading, spacing: 8) {
                    if post.isTrending {
                        Text("Trending")
                            .font(.caption)
                            .fontWeight(.bold)
                            .foregroundColor(.red)
                    }
                    Text(post.title)
                        .font(.headline)
                    Text(post.body)
                        .font(.subheadline)
                        .lineLimit(3)
                        .foregroundColor(.primary)
                }
                Spacer()
                // AsyncImage replacement for iOS 15 and newer
                if #available(iOS 15, *) {
                    AsyncImage(url: URL(string: post.imageUrl)) { phase in
                        if let image = phase.image {
                            image.resizable()
                                 .aspectRatio(contentMode: .fill)
                        } else if phase.error != nil {
                            Color.gray // Indicates an error.
                        } else {
                            Color.gray // Placeholder while loading.
                        }
                    }
                    .frame(width: 90, height: 90)
                    .cornerRadius(10)
                } else {
                    // Fallback for iOS versions before 15
                    Rectangle()
                        .fill(Color.gray)
                        .frame(width: 90, height: 90)
                        .cornerRadius(10)
                }
            }
            HStack {
                Text("10 mins ago") // Placeholder for dynamic timestamp
                    .font(.caption)
                    .foregroundColor(.secondary)
                Spacer()
                Button(action: {}) {
                    Image(systemName: "hand.thumbsup")
                    Text(String(post.numberOfReactions))
                }
                Button(action: {}) {
                    Image(systemName: "message")
                    Text(String(post.numberOfComments))
                }
                Button("Share", action: {})
            }
            .padding(.top, 5)
            Divider().padding(.horizontal, -16)
        }
        .padding(.bottom)
    }
}

解决方案

1. 修复模型拼写错误

AdminPost中的views: InT是拼写错误,应为Int,这会直接导致Codable解析失败,compactMap后数组为空:

struct AdminPost: Codable, Identifiable {
    @DocumentID var id: String?
    var title: String
    var body: String
    var tags: String
    var imageUrl: String 
    var timestamp: Date
    var isTrending: Bool 
    var views: Int // 修正拼写
    var numberOfComments: Int 
    var numberOfReactions: Int
    var isFeatured: Bool 
}

2. 处理Firestore日期解析

Firestore的Timestamp类型需要显式配置解码器才能正确解析为Swift的Date,修改fetchData中的解析逻辑:

self.posts = documents.compactMap { doc -> AdminPost? in
    let decoder = Firestore.Decoder()
    decoder.dateDecodingStrategy = .timestamp // 适配Firestore Timestamp
    return try? decoder.decode(AdminPost.self, from: doc.data())
}

同时确保Firestore中timestamp字段存储的是Timestamp类型,而非字符串或其他格式。

3. 调试解析错误

将try?替换为do-catch,打印具体解析错误,定位字段问题:

self.posts = documents.compactMap { doc -> AdminPost? in
    do {
        let post = try doc.data(as: AdminPost.self)
        return post
    } catch {
        print("解析文档失败: \(error),文档ID: \(doc.documentID)")
        return nil
    }
}

4. 主线程更新@Published变量

Firestore快照回调在后台线程,显式切换到主线程更新posts,确保SwiftUI能正确响应:

DispatchQueue.main.async {
    self.posts = documents.compactMap { ... }
}

5. 修复NewsContentView重复body定义

删除NewsContentView中重复的var body: some View定义,保留一个即可。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 15:27:02