Search code examples
iosswiftuitableviewcloudkitckasset

Images from CKAsset Load Out of Order in Swift


I am loading a tableview of images that are being fetched from a public CloudKit database as CKAssets. However, the images are loading out of order about two seconds until the correct image is loaded into the UIImageView of a custom UITableview cell. I know that the issue is that since the cell is reusable the image is still downloaded from CloudKit and displayed in any visible cell while a user is scrolling through the TableView before the correct image is shown in the image view. I am wondering if there is a fix to this in swift so that the image downloaded is only for that of a visible cell and not any previous cells.

Here is the code for cellForRowAtIndexPath:

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! PostsTableViewCell
    cell.userInteractionEnabled = false

    photoRecord = sharedRecords.fetchedRecords[indexPath.row]

    cell.photoTitle.text = photoRecord.objectForKey("photoTitle") as? String

    cell.photoImage.backgroundColor = UIColor.blackColor()
    cell.photoImage.image = UIImage(named: "stock_image.png")

    if let imageFileURL = imageCache.objectForKey(self.photoRecord.recordID) as? NSURL {
        cell.photoImage.image = UIImage(data: NSData(contentsOfURL: imageFileURL)!)
        cell.userInteractionEnabled = true
        print("Image Cached: \(indexPath.row)")
    } else {

        let container = CKContainer.defaultContainer()
        let publicDatabase = container.publicCloudDatabase
        let fetchRecordsImageOperation = CKFetchRecordsOperation(recordIDs:[self.photoRecord.recordID])
        fetchRecordsImageOperation.desiredKeys = ["photoImage"]
        fetchRecordsImageOperation.queuePriority = .VeryHigh

        fetchRecordsImageOperation.perRecordCompletionBlock = {(record:CKRecord?, recordID:CKRecordID?, error:NSError?) -> Void in
            if let imageRecord = record {
                NSOperationQueue.mainQueue().addOperationWithBlock() {
                    if let imageAsset = imageRecord.objectForKey("photoImage") as? CKAsset{
                        cell.photoImage.image = UIImage(data: NSData(contentsOfURL: imageAsset.fileURL)!)
                        self.imageCache.setObject(imageAsset.fileURL, forKey:self.photoRecord.recordID)
                        cell.userInteractionEnabled = true
                        }
                    }
                }
            }
        publicDatabase.addOperation(fetchRecordsImageOperation)
        }
    return cell
}

Thanks in advance!


Solution

  • There is latency between when your table view appears and when fetchRecordsImageOperation.perRecordCompletionBlock is called. Within that time the user may scroll the table view causing the table view cell to dequeue and requeue with a different indexPath and different data associated with it, if you do not check that the cell's index path is the same as when you constructed fetchRecordsImageOperation.perRecordCompletionBlock, this line: cell.photoImage.image = UIImage(data: NSData(contentsOfURL: imageAsset.fileURL)!) will cause the image to be placed in the cell that is already displaying different data. You can modify your completion block like so to avoid this.

    if let imageRecord = record {
                    NSOperationQueue.mainQueue().addOperationWithBlock() {
                        if let imageAsset = imageRecord.objectForKey("photoImage") as? CKAsset{
                            if indexPath == tableView.indexPathForCell(cell){
                                cell.photoImage.image = UIImage(data: NSData(contentsOfURL: imageAsset.fileURL)!)
                            }
                            self.imageCache.setObject(imageAsset.fileURL, forKey:self.photoRecord.recordID)
                            cell.userInteractionEnabled = true
                            }
                        }
                    }