Error loading a file from Firebase

Hi all,

I have been facing a problem while loading a file from the firebase storage. Basically in my code i have a data model which is the file/document (pdf in this case), a manager which goes into the database of Firebase to fetch the object and finally two views, one to list the files fetched and another one to show the document.

I can fetch all the documents with their specific data, pass them to my view and then show the document pdf in another view.

Everything seems correct but the problem comes when opening one of this docs. First, the doc doesn't open at first so you need to close the view and try to open the doc again. Then when i want to open another doc from my list, when i click on it the previous document is shown and not the correct one, so i need to do the same, close the view and click again on the doc to open the correct one.

So at the end what i'm looking for is to open the doc just clicking once but i don't know where is my problem.

List of docs:

1st time:

2nd time:

2nd doc but 1st time:

2nd doc and 2nd time clicking on it:

Here is my code

  • Model:
struct DocumentoPdf {
    let titulo: String
    let docUrl: URL?
    let id: String
    let storageUrl: String
}
  • Manager which fetches the documents from firebase:
final class DatabaseManager {

    [...]

    public func getNovedades(completion: @escaping ([DocumentoPdf]) -> Void) {
        database.collection("novedades").getDocuments { snapshot, error in
            guard let documents = snapshot?.documents.compactMap ({ $0.data() }), error == nil else {
                return
            }
            let novedades: [DocumentoPdf] = documents.compactMap({ dictionary in
                guard let id = dictionary["id"] as? String,
                      let titulo = dictionary["titulo"] as? String,
                      let docUrlString = dictionary["docUrl"] as? String,
                      let storageUrl = dictionary["storageUrl"] as? String else {
                          return nil
                      }
                let novedad = DocumentoPdf(titulo: titulo,
                                           docUrl: URL(string: docUrlString),
                                           id: id,
                                           storageUrl: storageUrl)
                return novedad
            })
            completion(novedades)
        }
    }

}
  • First View:
class NovedadesViewControllerViewModel {
    let docUrl: URL?
    var docData: Data?
    init(docUrl: URL?) {
        self.docUrl = docUrl
    }
}

class NovedadesViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, PDFViewDelegate {

    private var tableView =  UITableView()

    private var doc = PDFDocument()
    public func configureDocPdf(with viewModel: NovedadesViewControllerViewModel) {
        if let data = viewModel.docData {
            doc = PDFDocument(data: data)!
        }
        else if let url = viewModel.docUrl {
            // fetch doc & cache
            let task = URLSession.shared.dataTask(with: url) { [weak self] data, _, _ in
                guard let data = data else {
                    return
                }
                viewModel.docData = data
                DispatchQueue.main.async {
                    self?.doc = PDFDocument(data: data)!
                }
            }
            task.resume()
        }
    }

    private var novedades: [DocumentoPdf] = []
    private func obtenerNovedades() {
        DatabaseManager.shared.getNovedades() { [weak self] novedades in
            self?.novedades = novedades
            DispatchQueue.main.async {
                self?.tableView.reloadData()
            }
        }
    }

    override func viewDidLoad() {
        [...]
        obtenerNovedades()
        [...]
    }

    [... sections, rows in section...]

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let novedad = novedades[indexPath.row]
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        cell.textLabel?.text = novedad.titulo
        return cell
    }

    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let novedad = novedades[indexPath.row]
        switch indexPath.section {
        case 0:
            configureDocPdf(with: .init(docUrl: novedad.docUrl))
            let lectorPdfView = LectorPdfViewController(doc: self.doc, nameDoc: novedad.titulo)
            self.present(lectorPdfView, animated: true, completion: nil)
        default:
            break
        }
    }

}
  • Second View to load the pdf:
class LectorPdfViewController: UIViewController, PDFViewDelegate {

    private let navBar = UINavigationBar()

    private let pdf = PDFView()

    let nameDoc: String
    let doc: PDFDocument
    init(doc: PDFDocument, nameDoc: String) {
        self.doc = doc
        self.nameDoc = nameDoc
        super.init(nibName: nil, bundle: nil)
    }
    required init?(coder: NSCoder) {
        fatalError()
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemBackground
        view.addSubview(navBar)
        view.addSubview(pdf)

        pdf.document = self.doc
        pdf.delegate = self

        let item = UINavigationItem(title: self.nameDoc)
        navBar.setItems([item], animated: false)
        navBar.barTintColor = .systemBackground
    }

    override func viewDidLayoutSubviews() {
        navBar.frame = CGRect(x: 0,
                              y: 0,
                              width: view.frame.size.width,
                              height: 50)
        pdf.frame = CGRect(x: 0,
                           y: navBar.frame.height,
                           width: view.frame.size.width,
                           height: view.frame.size.height - navBar.frame.height)
    }
}

Please if someone can help me with this will be fantastic. I hope you understood my code and my problem. Sorry for my bad english

Thank you so much in advance

To ease the understanding of your question, please explain which ViewController is involved in each step:

Everything seems correct but the problem comes when opening one of this docs. First, the doc doesn't open at first so you need to close the view and try to open the doc again. Then when i want to open another doc from my list, when i click on it the previous document is shown and not the correct one, so i need to do the same, close the view and click again on the doc to open the correct one.

Could be that when you first open, document is not loaded yet. Hence does not display. When you close and reopen, it is fetched, hence can be displayed.

But need to understand your code to confirm.

Of course.

So i have two ViewControllers, one is called NovedadesViewController (1st image) and the other one called LectorPdfViewController (2nd 3rd 4th 5th images).

When the app launches, the first to appear is the NovedadesViewController, where all the files are listed in a table. Then, in that table when you click on one row, the LectorPdfViewController is called and here is where the specific pdf file is displayed.

When you finish "reading" the pdf you can close the presented view LectorPdfViewController and go back to NovedadesViewController with the table and all the files listed.

The problem is when i called to LectorPdfViewController because, first the pdf is not displayed as you can see in the second image. Then if i close LectorPdfViewController and select again the row of the table, then the pdf is displayed. Also, if now i select any other row from the NovedadesViewController the last pdf is displayed but not this new pdf.

Maybe you can understand the code better now.

If you have any idea on how to solve this, please tell me.

Thanks a lot for your reply!!!

If any of you still don't understand something, please tell me.

The problem you're describing (where something loads the second time you open it but not the first) is almost always a case of something happening asynchronously and the view not updating after the async code has finished. Looking at what you've posted, it looks like the first view controller's table didSelectRow function, configureDocPdf runs asynchronously and the pdf presenting code doesn't wait for it to finish before firing. So the first time you load the PDF, your URLSession is still trying to get the data when LectorPdfViewController is initialized with self.doc (which wouldn't have loaded yet).

Another way to test this would be to try and load different PDFs each time, and see if it's always displaying the previous PDF you tried to load. That would be because the URLSession finished the previous self.doc, but not the one you're trying now.

If that turns out to be the problem, you'll need to hold off loading the pdf view controller and presenting it until after the pdf data is downloaded. In the future, you might also take a look at the new async/await APIs which would prevent a bug like this from happening.

Hope this helps.

Error loading a file from Firebase
 
 
Q