tedcurrent
tedcurrent

Reputation: 431

Load image from Parse to UICollectionView cell without lag

I have a pretty elaborate problem and I think someone with extensive async knowledge may be able to help me.

I have a collectionView that is populated with "Picture" objects. These objects are created from a custom class and then again, these objects are populated with data fetched from Parse (from PFObject).

First, query Parse

func queryParseForPictures() {
    query.findObjectsInBackgroundWithBlock { (objects: [PFObject]?, err: NSError?) -> Void in
        if err == nil {
            print("Success!")
            for object in objects! {
                let picture = Picture(hashtag: "", views: 0, image: UIImage(named: "default")!)
                picture.updatePictureWithParse(object)
                self.pictures.insert(picture, atIndex: 0)
            }
            dispatch_async(dispatch_get_main_queue()) { [unowned self] in
                self.filtered = self.pictures
                self.sortByViews()
                self.collectionView.reloadData()
            }
        }
    }
}

Now I also get a PFFile inside the PFObject, but seeing as turning that PFFile into NSData is also an async call (sync would block the whole thing..), I can't figure out how to load it properly. The function "picture.updatePictureWithParse(PFObject)" updates everything else except for the UIImage, because the other values are basic Strings etc. If I would also get the NSData from PFFile within this function, the "collectionView.reloadData()" would fire off before the pictures have been loaded and I will end up with a bunch of pictures without images. Unless I force reload after or whatever. So, I store the PFFile in the object for future use within the updatePictureWithParse. Here's the super simple function from inside the Picture class:

func updateViewsInParse() {
    let query = PFQuery(className: Constants.ParsePictureClassName)
    query.getObjectInBackgroundWithId(parseObjectID) { (object: PFObject?, err: NSError?) -> Void in
        if err == nil {
            if let object = object as PFObject? {
                object.incrementKey("views")
                object.saveInBackground()
            }
        } else {
            print(err?.description)
        }
    }
}

To get the images in semi-decently I have implemented the loading of the images within the cellForItemAtIndexPath, but this is horrible. It's fine for the first 10 or whatever, but as I scroll down the view it lags a lot as it has to fetch the next cells from Parse. See my implementation below:

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    let cell = collectionView.dequeueReusableCellWithReuseIdentifier(Constants.PictureCellIdentifier, forIndexPath: indexPath) as! PictureCell

    cell.picture = filtered[indexPath.item]

    // see if image already loaded
    if !cell.picture.loaded {
        cell.loadImage()
    }
    cell.hashtagLabel.text = "#\(cell.picture.hashtag)"
    cell.viewsLabel.text = "\(cell.picture.views) views"
    cell.image.image = cell.picture.image

    return cell
}

And the actual fetch is inside the cell:

func loadImage() {
    if let imageFile = picture.imageData as PFFile? {
        image.alpha = 0
        imageFile.getDataInBackgroundWithBlock { [unowned self] (imageData: NSData?, err: NSError?) -> Void in
            if err == nil {
                self.picture.loaded = true
                if let imageData = imageData {
                    let image = UIImage(data: imageData)
                    self.picture.image = image
                    dispatch_async(dispatch_get_main_queue()) {
                        UIView.animateWithDuration(0.35) {
                            self.image.image = self.picture.image
                            self.image.alpha = 1
                            self.layoutIfNeeded()
                        }
                    }
                }
            }
        }
    }
}

I hope you get a feel of my problem. Having the image fetch inside the cell dequeue thing is pretty gross. Also, if these few snippets doesn't give the full picture, see this github link for the project: https://github.com/tedcurrent/Anonimg

Thanks all!

/T

Upvotes: 2

Views: 505

Answers (1)

Janny
Janny

Reputation: 11

Probably a bit late but when loading PFImageView's from the database in a UICollectionView I found this method to be much more efficient, although I'm not entirely sure why. I hope it helps. Use in your cellForItemAtIndexPath in place of your cell.loadImage() function.

if let value = filtered[indexPath.row]["imageColumn"] as? PFFile {
    if value.isDataAvailable {
        cell.cellImage.file = value       //assign the file to the imageView file property
        cell.cellImage.loadInBackground() //loads and does the PFFile to PFImageView conversion for you
    }
}

Upvotes: 1

Related Questions