Xcode UIKit TableViewController无法显示Cloud Firestore数据求助
问题:Firestore数据获取成功但TableView无法渲染单元格
原本App运行正常,将TableView(每个单元格包含图片、标题和副标题,点击可流式播放视频)的数据改为从Cloud Firestore获取后,ViewModel的fetchData方法能成功获取数据(打印data可见内容),但TableView的viewModel.videos.count始终为0,无法渲染单元格。
相关代码
ListOfVideoLessonsTableViewController.swift
import UIKit import AVKit import AVFoundation import FirebaseFirestore import SwiftUI class ListOfVideoLessonsTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { let pinchGesture = UIPinchGestureRecognizer() @ObservedObject private var viewModel = VideosViewModel() var player = AVPlayer() var playerViewController = AVPlayerViewController() @IBOutlet var table: UITableView! override func viewDidLoad() { super.viewDidLoad() self.viewModel.fetchData() self.title = "Video Lessons" table.delegate = self table.dataSource = self } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { print("videos count = ", viewModel.videos.count) return viewModel.videos.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) let video = viewModel.videos[indexPath.row] tableView.tableFooterView = UIView() //configure cell cell.textLabel?.text = video.name cell.detailTextLabel?.text = video.lessonName cell.accessoryType = .disclosureIndicator let imageName = UIImage(named: video.imageName) cell.imageView?.image = imageName!.withRenderingMode(.alwaysOriginal) let backgroundView = UIView() backgroundView.backgroundColor = UIColor(named: "VideoLessonsCellHighlighted") cell.selectedBackgroundView = backgroundView cell.textLabel?.font = UIFont(name: "Helvetica-Bold", size: 14) cell.detailTextLabel?.font = UIFont(name: "Helvetica", size: 12) return cell } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 100 } func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { tableView.deselectRow(at: indexPath, animated: true) playVideo(at: indexPath) } func tableView(_ tableView: UITableView, didHighlightRowAt indexPath: IndexPath) { if let cell = tableView.cellForRow(at: indexPath) { cell.contentView.backgroundColor = UIColor(named: "VideoLessonsCellHighlighted") cell.textLabel?.highlightedTextColor = UIColor(named: "textHighlighted") cell.detailTextLabel?.highlightedTextColor = UIColor(named: "textHighlighted") } } func tableView(_ tableView: UITableView, didUnhighlightRowAt indexPath: IndexPath) { if let cell = tableView.cellForRow(at: indexPath) { cell.contentView.backgroundColor = nil } } func playVideo(at indexPath: IndexPath){ let selectedVideo = viewModel.videos[indexPath.row] let url = URL(string: selectedVideo.fileURL) player = AVPlayer(url: url!) playerViewController.player = player self.present(playerViewController, animated: true, completion: { self.playerViewController.player?.play() }) } }
VideosViewModel.swift
import Foundation import FirebaseAnalytics import FirebaseFirestore class VideosViewModel: ObservableObject { @Published var videos = [Video]() private var db = Firestore.firestore() func fetchData() { db.collection("videos").addSnapshotListener { [self] (querySnapshot, error) in guard let documents = querySnapshot?.documents else { print("No Documents") return } self.videos = documents.map { (queryDocumentSnapshot) -> Video in let data = queryDocumentSnapshot.data() let name = data["name"] as? String ?? "" let imageName = data["imageName"] as? String ?? "" let lessonName = data["lessonName"] as? String ?? "" let fileURL = data["fileURL"] as? String ?? "" print(data) return Video(name: name, imageName: imageName, lessonName: lessonName, fileURL: fileURL) } } } }
解决方案
1. 适配UIKit中的ObservableObject监听
UIKit不会像SwiftUI那样自动响应ObservableObject的变化,需要手动订阅@Published属性的更新:
- 导入Combine框架:
import Combine
- 在ViewController中添加存储订阅的属性:
private var cancellables = Set<AnyCancellable>()
- 将
@ObservedObject替换为@StateObject(iOS 14及以上版本推荐):
@StateObject private var viewModel = VideosViewModel()
- 在
viewDidLoad中添加监听,触发TableView刷新:
override func viewDidLoad() { super.viewDidLoad() self.viewModel.fetchData() self.title = "Video Lessons" table.delegate = self table.dataSource = self // 监听videos数组变化,主线程更新TableView viewModel.$videos .receive(on: DispatchQueue.main) .sink { [weak self] _ in self?.table.reloadData() } .store(in: &cancellables) }
2. 确保Firestore回调在主线程更新数据(可选但稳妥)
虽然@Published会自动切换到主线程,但手动在Firestore回调中切换到主线程更新videos数组,可避免潜在线程问题:
func fetchData() { db.collection("videos").addSnapshotListener { [self] (querySnapshot, error) in guard let documents = querySnapshot?.documents else { print("No Documents") return } DispatchQueue.main.async { self.videos = documents.map { (queryDocumentSnapshot) -> Video in let data = queryDocumentSnapshot.data() let name = data["name"] as? String ?? "" let imageName = data["imageName"] as? String ?? "" let lessonName = data["lessonName"] as? String ?? "" let fileURL = data["fileURL"] as? String ?? "" print(data) return Video(name: name, imageName: imageName, lessonName: lessonName, fileURL: fileURL) } } } }
3. 验证Video模型字段匹配
确认Firestore集合中的字段名(name、imageName、lessonName、fileURL)与ViewModel中解析的字段完全一致,避免因字段名不匹配导致的空值或未正确初始化Video对象。
原因说明
问题核心在于UIKit不具备SwiftUI的自动状态更新机制:当ViewModel的videos数组被Firestore数据更新后,TableView没有收到任何刷新通知,因此始终显示初始的count=0。通过手动订阅@Published属性的变化,调用table.reloadData(),就能让TableView响应数据更新并渲染单元格。
内容的提问来源于stack exchange,提问作者Vugar
相关产品推荐
相关产品推荐

