LampPost
LampPost

Reputation: 866

Issue trying to complete Firebase Storage download before showing tableview

I have a table view where depending on the cell class it will download an image from Firebase. I've noticed when using the app that cells with the same cell identifier will show the previous downloaded image before showing the new one. This is what I have before changing it.

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    if tableData[indexPath.row]["Image"] != nil {
        let cell = tableView.dequeueReusableCell(withIdentifier: "imageNotesData", for: indexPath) as! ImageNotesCell
        cell.notes.delegate = self
        cell.notes.tag = indexPath.row
        cell.notes.text = tableData[indexPath.row]["Notes"] as! String
        guard let imageFirebasePath = tableData[indexPath.row]["Image"] else {
            return cell }
        let pathReference = Storage.storage().reference(withPath: imageFirebasePath as! String)
        pathReference.getData(maxSize: 1 * 1614 * 1614) { data, error in
            if let error = error {
                print(error)
            } else {
                let image = UIImage(data: data!)
                cell.storedImage.image = image
            }
        }
        return cell
    }
    else {
        let cell = tableView.dequeueReusableCell(withIdentifier: "notesData", for: indexPath) as! NotesCell
        //let noteString = tableData[indexPath.row]["Notes"] as! String
        cell.notes.text = tableData[indexPath.row]["Notes"] as! String
        cell.notes.delegate = self
        cell.notes.tag = indexPath.row
        return cell
    }
}

Knowing that this is not a good user experience and that it looks clunky, I tried to move the pathReference.getData to where I setup the data but the view appears before my images finish downloading. I have tried to use a completion handler but I'm still having issues.

override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(true)
            getSectionData(userID: userID, city: selectedCity, completion: {(sectionString) in
            self.setupTableCellView(userID: userID, city: selectedCity, section: sectionString) { (tableData) in
                DispatchQueue.main.async(execute:  {
                    self.cityName?.text = selectedCity
                    self.changeSections.setTitle(sectionString, for: .normal)
                    self.currentSectionString = sectionString
                    self.setupTableData(tableDataHolder: tableData)
                })
            }
        })
}

func setupTableCellView(userID: String, city: String, section: String, completion: @escaping ([[String:Any]]) -> () ) {
        let databaseRef = Database.database().reference().child("Users").child(userID).child("Cities").child(city).child(section)
        var indexData = [String:Any]()
        var indexDataArray = [[String:Any]]()
        databaseRef.observeSingleEvent(of: .value, with: { (snapshot) in
            for dataSet in snapshot.children {
                let snap = dataSet as! DataSnapshot
                //let k = snap.key
                let v = snap.value
                indexData = [:]
                for (key, value) in v as! [String: Any] {
                    //indexData[key] = value
                    if key == "Image" {
                        //let pathReference = Storage.storage().reference(withPath: value as! String)
                        print("before getImageData call")
                        self.getImageData(pathRef: value as! String, completion: {(someData) in
                            print("before assigning indexData[key]")
                            indexData[key] = someData
                            print("after assigning indexData[key]")
                        })
                    } else {
                        indexData[key] = value
                    }
                }
                indexDataArray.append(indexData)
            }
            completion(indexDataArray)
        })
    }

func getImageData(pathRef: String, completion: @escaping(UIImage) -> ()) {
    let pathReference = Storage.storage().reference(withPath: pathRef as! String)
    pathReference.getData(maxSize: 1 * 1614 * 1614, completion: { (data, error) in
        if let error = error {
            print(error)
        } else {
            let image = UIImage(data:data!)
            print("called before completion handler w/ image")
            completion(image!)
        }
    })
}

I don't know if I am approaching this the right way but I think I am. I'm also guessing that the getData call is async and that is why it will always download after showing the table view.

Upvotes: 0

Views: 46

Answers (2)

Fattie
Fattie

Reputation: 12651

You can't do this.

Make the request from Firebase.

Over time, you will get many replies - all the information and all the changing information.

When each new item arrives - and don't forget it may be either an addition or deletion - alter your table so that it displays all the current items.

That's OCC!

OCC is "occasionally connected computing". A similar phrase is "offline first computing". So, whenever you use any major service you use every day like Facebook, Snapchat, etc that is "OCC": everything stays in sync properly whether you do or don't have bandwidth. You know? The current major paradigm of device-cloud computing.

Upvotes: 1

Jake T.
Jake T.

Reputation: 4378

Edit - See Fattie's comments about prepareForReuse()!

With reusable table cells, the cells will at first have the appearance they do by default / on the xib. Once they're "used", they have whatever data they were set to. This can result in some wonky behavior. I discovered an issue where in my "default" case from my data, I didn't do anything ecause it already matched the xib, but if the data's attributes were different, I updated the appearance. The result was that scrolling up and down really fast, some things that should have had the default appearance had the changed appearance.

One basic solution to just not show the previous image would be to show a place holder / empty image, then call your asynchronous fetch of the image. Not exactly what you want because the cell will still show up empty...

Make sure you have a local store for the images, otherwise you're going to be making a server request for images you already have as you scroll up and down!

I'd recommend in your viewDidLoad, call a method to fetch all of your images at once, then, once you have them all, in your success handler, call self.tableview.reloadData() to display it all.

Upvotes: 0

Related Questions