Search code examples
iosswiftparse-platformpfquery

Parse getDataInBackgroundWithBlock not fetching in order


I am fetching a string, NSDate and a PFFile from my Parse class to populate the collection view cells

All the cells load with image, date, info correctly. The info and date are ordered correctly (by ascending date). But every now and then when I build some of the images will be in a different cell. Im really scratching my head with this. Im guessing it has something to do with how I'm calling mixPhoto.getDataInBackgroundWithBlock({

I did try and use dispatch_async(dispatch_get_main_queue())

Still no luck... Heres my code, anyone got any ideas?

@IBOutlet weak var collectionView1: UICollectionView!


var mixPhotoArray : Array<UIImage> = []
var mixInfoArray: Array <String> = []
var mixDateArray: Array <NSDate> = []

override func viewDidLoad() {
    super.viewDidLoad()
    collectionView1.delegate = self;
    collectionView1.dataSource = self;

    self.queryParseMethod()
    self.getImageData()

    // Uncomment the following line to preserve selection between presentations
    // self.clearsSelectionOnViewWillAppear = false

    // Do any additional setup after loading the view.
}


func getImageData() {
    var query = PFQuery(className: "musicMixes")
    query.orderByAscending("date")
    query.findObjectsInBackgroundWithBlock { (objects: [AnyObject]!, error: NSError!) -> Void in
    for object in objects {


    let mixPhoto = object["mixPhoto"] as PFFile

        mixPhoto.getDataInBackgroundWithBlock({
            (imageData: NSData!, error: NSError!) -> Void in
            if (error == nil) {
                dispatch_async(dispatch_get_main_queue()) {
                    let image = UIImage(data:imageData)
                    //image object implementation
                    self.mixPhotoArray.append(image!)
                    println(self.mixPhotoArray[0])
                    self.collectionView1.reloadData()

                }
            }
            else {
                println("error!!")
            }

        })//getDataInBackgroundWithBlock - end

        }



    }//for - end

}

    func queryParseMethod() {

    var query = PFQuery(className: "musicMixes")
    query.orderByAscending("date")
    query.findObjectsInBackgroundWithBlock { (objects: [AnyObject]!, error: NSError!) -> Void in
        if error == nil {
            for object in objects {


    let mixPhoto = object["mixPhoto"] as PFFile




        let mixInfo = object["info"] as String
        let dateForText = object["date"] as NSDate


        //self.collectionView1.reloadData()

            self.mixDateArray.append(dateForText)
            self.mixInfoArray.append(mixInfo)
            self.collectionView1.reloadData()


                }//for - end
        }
        }
} // end of queryParseMethod


override func didReceiveMemoryWarning() {
    super.didReceiveMemoryWarning()




        }



/*
// MARK: - Navigation

// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    // Get the new view controller using [segue destinationViewController].
    // Pass the selected object to the new view controller.
}
*/

// MARK: UICollectionViewDataSource

 func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
    //#warning Incomplete method implementation -- Return the number of sections
    return 1
}


 func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
    //#warning Incomplete method implementation -- Return the number of items in the section
     println("I have \(mixPhotoArray.count) Images")
    return mixInfoArray.count


}

  //func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
  func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {

    let cell:StreamCollectionViewCell = collectionView1.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as StreamCollectionViewCell

   cell.mixImage.image = mixPhotoArray[indexPath.item]
    cell.infoLabel.text = mixInfoArray[indexPath.item]

    // NSDate array into cell
    var dateFormatter = NSDateFormatter()
    dateFormatter.dateFormat = "yyyy-MM-dd"
   cell.mixDateLabel.text = dateFormatter.stringFromDate(mixDateArray[indexPath.item])






     return cell
}

Solution

  • Like Wain said, I believe the main issue is that since your images are downloading at different speeds, they're not necessarily being appended to your array in order. Instead of recommending that you use a dictionary though, here's what I would recommend to circumvent that problem while still using an array:

    // Declare your mixPhotoArray such that it can store optionals
    var mixPhotoArray : Array<UIImage?> = []
    
    func getImageData() {
    
        var query = PFQuery(className: "musicMixes")
        query.orderByAscending("date")
        query.findObjectsInBackgroundWithBlock { (objects: [AnyObject]!, error: NSError!) -> Void in
            // Initialize your array to contain all nil objects as
            // placeholders for your images
            self.mixPhotoArray = [UIImage?](count: objects.count, repeatedValue: nil)
            for i in 0...objects.count - 1 {
    
                let object: AnyObject = objects[i]
                let mixPhoto = object["mixPhoto"] as PFFile
    
                mixPhoto.getDataInBackgroundWithBlock({
                    (imageData: NSData!, error: NSError!) -> Void in
                    if (error == nil) {
                        dispatch_async(dispatch_get_main_queue()) {
                            let image = UIImage(data:imageData)
                            // Replace the image with its nil placeholder
                            // and do so using the loop's current index
                            self.mixPhotoArray[i] = image
                            println(self.mixPhotoArray[i])
                            self.collectionView1.reloadData()
                        }
                    }
                    else {
                        println("error!!")
                    }
    
                })
    
            }
    
        }
    
    }
    

    Then within collectionView:cellForItemAtIndexPath, you can set the image conditionally so that it only appears once its ready:

    if mixPhotoArray[indexPath.item] != nil {
        cell.mixImage.image = mixPhotoArray[indexPath.item]
    }