Home > Software design >  How to fetch data from Cloud Firestore to Xcode Uikit TableViewController?
How to fetch data from Cloud Firestore to Xcode Uikit TableViewController?

Time:09-13

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.

  • Related