Found nil while unwrapping optional. Optionals should be downloaded from firestore

Viewed 68

enter image description hereThis is the tableViewController streaming video when user taps on a row. Just Succeeded to display "name" and "lessonName" in the tableviewcontroller but it finds nil while unwrapping images and I think it will find nil while unwrapping video file "fileURL" and even video player. Any help is appreciated, thank you in advance.

import UIKit
import AVKit
import AVFoundation
import FirebaseFirestore
import Combine

class ListOfVideoLessonsTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
    
    
    let pinchGesture = UIPinchGestureRecognizer()
    
    private var viewModel = VideosViewModel()
    private var cancellable: AnyCancellable?
    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.
        
        cancellable = viewModel.$videos.sink { _ in
            DispatchQueue.main.async{
                self.table.reloadData()
            }
        }



    }

    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(.alwaysTemplate)
        
        
        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()
            })
           
        }
}

This is the Struct of the video files.

import Foundation

struct Video {
    var name: String
    var imageName: String
    var lessonName: String
    var fileURL: String
}

This is the viewModel

import Foundation
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 ?? ""
                videos.append(Video(name: name, imageName: imageName, lessonName: lessonName, fileURL: fileURL))
            
        
                
                print(data)


                return Video(name: name, imageName: imageName, lessonName: lessonName, fileURL: fileURL)
                
              
    
            }
        }
    }
}

This is what throws when user taps the row now. Also it is interesting why it shows "video count = 0" four times, then it shows "video count = 2 three times"? Am I calling data not in proper place in the code or it is the result of downloading timing of the data?

videos count =  0
videos count =  0
videos count =  0
["lessonName": Lesson Name, "name": Name 1, "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]
["fileURL": gs://tff-sample.appspot.com/Video Lessons/mixkit-meadow-surrounded-by-trees-on-a-sunny-afternoon-40647.mp4, "lessonName": Lesson Name, "imageName": gs://tff-sample.appspot.com/Video Images/1024x1024.png, "name": Name 2]
videos count =  2
videos count =  2
videos count =  2
TFF_Sample/ListOfVideoLessonsTableViewController.swift:72: Fatal error: Unexpectedly found nil while unwrapping an Optional value
2022-09-12 12:24:04.606689+0400 TFF Sample[12286:90358] TFF_Sample/ListOfVideoLessonsTableViewController.swift:72: Fatal error: Unexpectedly found nil while unwrapping an Optional value
1 Answers

Your problem is in this line here:

let imageName = UIImage(named: video.imageName)

You are trying to create an UIImage using a variable containing the URL from which you should fetch the image:

"imageName": gs://tff-sample.appspot.com/Video Images/1024x1024.png

So, you need to change your Video struct to:

struct Video {
    var name: String
    var imageData: Data
    var lessonName: String
    var file URL: String
}

...and in your VideosViewModel, use a URLSession to load the image data from that URL in imageName and store it into the imageData property.

And to configure your tableview cell:

let image = UIImage(data: video.imageData)
cell.imageView?.image = image....

And if I can give you one piece of advice, never ever use force unwrapping in production code unless you are 100% sure the variable is not nil.

Hope it helps :)

Related