Search code examples
swiftuicollectionviewuiprogressview

CollectionView Cell and Progress Bar - Progress Bar showing in wrong Cell after scrolling


Ive been searching for a answer to this one for days now and cant seem to figure it out. I have a Collection View with custom cell. When you double tap a cell in the Collection View it will either download a file or delete it if its been downloaded before.

During the download a progress bar displays the progress of the download then displays a small icon in the top left corner. When deleting it removes the icon.

enter image description here

If you download from one cell and delete from another while first download is in progress it works fine but only if both cells were visible within the collection view.

enter image description here

if i download from one cell, then scroll offscreen and delete from a cell that is not in same screen as the cell that is being download from, it removes the corner image as usual then displays the progress bar of the cell that is being download from.

enter image description here

I don't know if this is an error with how i am reusing cells??? It doesn't seem to have anything to do with how i am updating the cell or collection view which works in all cases except after scrolling.

Below is 2 functions that download or delete file:

func downloadDataToDevice(cell: JourneyCollectionViewCell, selectedIndexPath: IndexPath){

    let downloadedAudio = PFObject(className: "downloadedAudio")
    // save all files with unique name / object id
    let selectedObjectId = self.partArray[selectedIndexPath.item].id
    let selectedPartName = self.partArray[selectedIndexPath.item].name

    let query = PFQuery(className: "Part")
    query.whereKey("objectId", equalTo: selectedObjectId)
    query.getFirstObjectInBackground { (object, error) in

        if error != nil || object == nil {
            print("No object for the index selected.")
        } else {
            //print("there is an object, getting the file.")
            downloadedAudio.add(object?.object(forKey: "partAudio") as! PFFile, forKey: selectedPartName)
            let downloadedFile = object?.object(forKey: "partAudio") as! PFFile

            // get the data first so we can track progress
            downloadedFile.getDataInBackground({ (success, error) in
                if (success != nil) {
                    // pin the audio if there is data
                    downloadedAudio.pinInBackground(block: { (success, error) in
                        if success {

                            // reload the cell
                            self.reloadCell(selectedIndexPath: selectedIndexPath, hideProgress: true, hideImage: false, cell: cell)
                            self.inProgress -= 1
                            cell.isUserInteractionEnabled = true

                        }
                    })
                }
            // track the progress of the data
            }, progressBlock: { (percent) in
                self.activityIndicatorView.stopAnimating()
                cell.progessBar.isHidden = false
                //cell.progessBar.transform = cell.progessBar.transform.scaledBy(x: 1, y: 1.1)
                cell.contentView.bringSubview(toFront: cell.progessBar)
                cell.progessBar.setProgress(Float(percent) / Float(100), animated: true)
                cell.isUserInteractionEnabled = false
            })
        }
    }
}

func removeDataFromDevice(cell: JourneyCollectionViewCell, selectedIndexPath: IndexPath, object: PFObject) {

        let selectedPartName = self.partArray[selectedIndexPath.item].name

        // unpin the object from the LocalDataStore
        PFObject.unpinAll(inBackground: [object], block: { (success, error) in
            if success {
                // reduce inProgress
                self.inProgress -= 1
                self.reloadCell(selectedIndexPath: selectedIndexPath, hideProgress: true, hideImage: true, cell: cell)
            }
        })
}

and this is how I'm reloading the cell

func reloadCell(selectedIndexPath: IndexPath, hideProgress: Bool, hideImage: Bool, cell: JourneyCollectionViewCell) {
    cell.progessBar.isHidden = hideProgress
    cell.imageDownloaded.isHidden = hideImage
    self.collectionView.reloadItems(at: [selectedIndexPath])
}

----------- EDIT -------------

This is my cellForItem at function. Presently i am using a query to look on local drive and see if the file exists and then adding the corner image if it is. This is the first time i have used a query in this place, usually it is a query at login to populate an array but that is for a more static collection of data than what i am trying to achieve here by letting the user download and delete files.

func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {

    let cell: JourneyCollectionViewCell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! JourneyCollectionViewCell

        cell.imageCell.file = self.partArray[indexPath.item].image
        cell.imageCell.loadInBackground()
        cell.imageCell.layer.masksToBounds = true

    // not sure if its good to run a query here as its constantly updated.
    // query if file is on LDS and add image to indicate
    let cellPartName = self.partArray[indexPath.item].name
    let checkQuery = PFQuery(className: "downloadedAudio")
        checkQuery.whereKeyExists(cellPartName)
        checkQuery.fromLocalDatastore()
        checkQuery.getFirstObjectInBackground(block: { (object, error) in
        if error != nil || object == nil {
            //print("The file does not exist locally on the device, remove the image.")
            cell.imageDownloaded.isHidden = true
            cell.imageDownloaded.image = UIImage(named: "")
            cell.progessBar.isHidden = true

        } else {
            //print("the file already exists on the device, add the image.")
            cell.contentView.bringSubview(toFront: cell.imageDownloaded)
            cell.imageDownloaded.isHidden = false
            cell.imageDownloaded.image = UIImage(named: "download-1")

            }
        })
    return cell
} 

Solution

  • So i tried placing the progress view programatically, i tried prepareForReuse in the custom cell class, neither resolved this issue directly, though i will keep using prepareForReuse as i think its a cleaner way to manage the cell than i had been.

    What seems to have worked was relocating the cell within the progressBlock

    if let downloadingCell = self.collectionView.cellForItem(at: selectedIndexPath) as? JourneyCollectionViewCell {                            downloadingCell.progessBar.isHidden = false
        downloadingCell.contentView.bringSubview(toFront: downloadingCell.progessBar)
        downloadingCell.progessBar.setProgress(Float(percent) / Float(100), animated: true)
        downloadingCell.setNeedsDisplay()
        downloadingCell.isUserInteractionEnabled = false
    }
    

    enter image description here