I try to display data from from cloud firestore in tableview. Before trying this my app worked fine. I have a tableview with basic cells image title and a subtitle in each row. Also a URL of video files which start streaming when user taps a cell. Now I want those cells to display data from Cloud Firestore. Appreciate any help. Thank you in advance.
This is the swift file where everything worked before I started to try to list the data from cloud Firestore.
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
// Do any additional setup after loading the view.
}
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()
})
}
}
I have a VideoViewModel.swift file
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)
}
}
}
}
When I print print("videos count = ", viewModel.videos.count) It shows 000 videos,
videos count = 0
videos count = 0
videos count = 0
When I print print(data) it shows data from firebase firestore.
["lessonName": Lesson Name, "name": Name, "fileURL": gs://tff-sample.appspot.com/Video Lessons/mixkit-hands-of-a-man-playing-on-a-computer-43527.mp4, "imageName": gs://tff-sample.appspot.com/Video Images/1024x1024.png]
["lessonName": LessonName, "name": Name 2, "fileURL": gs://tff-sample.appspot.com/Video Lessons/mixkit-meadow-surrounded-by-trees-on-a-sunny-afternoon-40647., "imageName": gs://tff-sample.appspot.com/Video Images/1024x1024.png]
CodePudding user response:
Since ObservedObject is designed for SwiftUI, and your code is using UIKit. Therefore after fetching data, the ViewModel is not being observed to let ViewController know if there is any update.
To solve that issue you can use Combine instead. You access the @Published property wrapper’s projected value (by prefixing its name with $), which gives us access to a Combine publisher just for that property. You can then subscribe to that publisher using the same sink API like this: viewModel.$videos.sink
import UIKit
import AVKit
import AVFoundation
import FirebaseFirestore
import Combine
class ListOfVideoLessonsTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
let pinchGesture = UIPinchGestureRecognizer()
private var viewModel = VideosViewModel()
// `AnyCancellable` belongs to Combine SDK
private var cancellable: AnyCancellable?
.......
override func viewDidLoad() {
super.viewDidLoad()
self.viewModel.fetchData()
self.title = "Video Lessons"
table.delegate = self
table.dataSource = self
// Do any additional setup after loading the view.
cancellable = viewModel.$videos.sink { _ in
DispatchQueue.main.async {
self.table.reloadData()
}
}
}
}
You need to keep track of the cancellable object that Combine returns when you start the subscription using sink, since that subscription will only remain valid for as long as the returned AnyCancellable instance is retained. This will avoid memory leaks.