Swift: UIImages appearing out of order in table view

385 Views Asked by At

I'm querying images from my Parse backend, and displaying them in order in a UITableView. Although I'm downloading and displaying them one at a time, they're appearing totally out of order in my table view. Each image (album cover) corresponds to a song, so I'm getting incorrect album covers for each song. Would someone be so kind as to point out why they're appearing out of order?

 class ProfileCell: UITableViewCell {

        @IBOutlet weak var historyAlbum: UIImageView!

    }

    class ProfileViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    @IBOutlet weak var tableView: UITableView!

    var historyAlbums = [PFFile]()
    var albumCovers = [UIImage]() 

   // An observer that reloads the tableView
    var imageSet:Bool = false {
            didSet {
                if imageSet {

                    // Reload tableView on main thread
                    dispatch_async(dispatch_get_global_queue(Int(QOS_CLASS_USER_INITIATED.value), 0)) { // 1
                        dispatch_async(dispatch_get_main_queue()) { // 2
                            self.tableView.reloadData() // 3
                        }
                    }
                }
            }
        }

        // An observer for when each image has been downloaded and appended to the albumCovers array. This then calls the imageSet observer to reload tableView.
        var dataLoaded:Bool = false {
            didSet {
                if dataLoaded {

                    let albumArt = historyAlbums.last!
                    albumArt.getDataInBackgroundWithBlock({ (imageData, error) -> Void in

                        if error == nil {
                            if let imageData = imageData {
                                let image = UIImage(data: imageData)
                                self.albumCovers.append(image!)
                            }

                        } else {

                            println(error)

                        }
                        self.imageSet = true
                    })
                }
                self.imageSet = false
            }
        }

override func viewDidLoad() {
        super.viewDidLoad()

// Queries Parse for each image
var query = PFQuery(className: "Songs")
        query.whereKey("user", equalTo: PFUser.currentUser()!.email!)
        query.orderByDescending("listenTime")
        query.limit = 20
        query.findObjectsInBackgroundWithBlock({ (objects, error) -> Void in
            if error == nil {

                if let objects = objects as? [PFObject] {
                    for object in objects {
if let albumCover = object["albumCover"] as? PFFile {

                            // Appending each image to albumCover array to convert from PFFile to UIImage
                            self.historyAlbums.append(albumCover)

                        }

                        self.dataLoaded = true

                    }

                }

            } else {

                println(error)

            }

            self.dataLoaded = false

        })
    }

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

    var profileCell = tableView.dequeueReusableCellWithIdentifier("ProfileCell", forIndexPath: indexPath) as! ProfileCell

    profileCell.historyAlbum.image = albumCovers[indexPath.row]

        return profileCell

    }

 }

}
1

There are 1 best solutions below

2
On BEST ANSWER

The reason you are getting them out of order is that you are firing off background tasks for each one individually.

You get the list of objects all at once in a background thread. That is perfectly fine. Then once you have that you call a method (via didset) to iterate through that list and individually get each in their own background thread. Once each individual thread is finished it adds it's result to the table array. You have no control on when those background threads finish.

I believe parse has a synchronous get method. I'm not sure of the syntax currently. Another option is to see if you can "include" the image file bytes with the initial request, which would make the whole call a single background call.

Another option (probably the best one) is to have another piece of data (a dictionary or the like) that marks a position to each of your image file requests. Then when the individual background gets are finished you know the position that that image is supposed to go to in the final array. Place the downloaded image in the array at the location that the dictionary you created tells you to.

That should solve your asynchronous problems.