Sporadic behaviour in UITableViewCells when loading images

213 views Asked by At

I have a Table View that presents comments. Each comment includes a label and a UIImage that presents the authors profile image. There is no pattern to the way images are appearing when the table is loaded. Sometimes they load fine. Other times, the placeholder image appears and the image never loads at all. If I scroll, the behavior varies. I'm using AlamofireImage.

After some research I came across this post where the recommendation is to call

self.tableView.beginUpdates()
self.tableView.endUpdates()

in AlamofireImage's completion handler. This had no effect at all.

Here is my original code:

func tableView(_ tableView: UITableView, cellForRowAt.....{
...
    let myURL = Comment.profileImageURL
            if Comment.profileImageURL != nil {
                profileImage.af_setImage(withURL: myURL!, placeholderImage: #imageLiteral(resourceName: "Profile Image"))
            } else {
                profileImage.image = UIImage(named: "Profile Image")
            }

And here is the update based on this question's recommendation (last answer on page):

let myURL = comment.profileImageURL
     if comment.profileImageURL != nil {
            cell.profileImage.af_setImage(
                            withURL: myURL!,
                            placeholderImage: #imageLiteral(resourceName: "Profile Image"),
                            filter: nil,
                            imageTransition: UIImageView.ImageTransition.crossDissolve(0.5),
                            runImageTransitionIfCached: false) {
                                // Completion closure
                                response in
                                // Check if the image isn't already cached
                                if response.response != nil {
                                    // Force the cell update
                                    self.tableView.beginUpdates()
                                    self.tableView.endUpdates()
                                }
                        }
     } else {
           cell.profileImage.image = UIImage(named: "Profile Image")
      }

====EDIT====

Adding additional information in the hopes it will help: I have tried setting the profileImage.image = nil as suggested below. I have also cancel the download in prepareForReuse() as suggested below to no avail.

Before calling the image, I am doing some configuration on the UIImageView

profileImage.layer.cornerRadius = profileImage.bounds.size.width / 2
profileImage.clipsToBounds = true
profileImage.layer.borderWidth = 2
profileImage.layer.borderColor = UIColor(red:222/255.0, green:225/255.0, blue:227/255.0, alpha: 1.0).cgColor

The following is the imageView setting in XCode: enter image description here

Finally the issue itself. About 50% of the time I move back and forth between views or scroll, placeholder images appear in place of the actual image. In fact. The place holders often appear on first load.

enter image description here enter image description here

===EDIT#2===

I'm trying to follow Ashley Mills second suggestion. I check the Image URL in the completion handler of AlamofireImage and the results are odd. I just loaded the table view. All images loaded fine as shown below:

enter image description here

I print out the the image URL when the Completion Handler fires and it looks fine:

I'm in completion Optional(http://www.smarttapp.com/Portals/0/Users/018/18/18/sballmer.jpg) I'm in completion Optional(http://www.smarttapp.com/Portals/0/Users/018/18/18/sballmer.jpg) I'm in completion Optional(http://www.smarttapp.com/Portals/0/Users/011/11/11/Elon%20Musk.jpeg) I'm in completion Optional(http://www.smarttapp.com/Portals/_default/Users/001/01/1/Martin%20Profile.jpg)

Using the Navigation Bar, I back out, then reload and the images are all place holder images as shown:

enter image description here

The print out is as follows:

I'm in completion nil I'm in completion nil I'm in completion nil

Finally this is the code where the image is loaded and the completion handler:

 let myURL = Comment.profileImageURL
        if Comment.profileImageURL != nil {
           // profileImage.af_setImage(withURL: myURL!, placeholderImage: #imageLiteral(resourceName: "Profile Image"))
            profileImage.af_setImage(
                withURL: myURL!,
                placeholderImage: #imageLiteral(resourceName: "Profile Image"),
                filter: nil,
                completion:{ image in
                    print("I'm in completion")
                    print(image.response?.url)
                    self.setNeedsLayout()
                    self.layoutIfNeeded()
            })

        } else {
            profileImage.image = UIImage(named: "Profile Image")
        }

I've tried everything at this point. Please help.

2

There are 2 answers

1
Tamás Sengel On

UITableViewController reuses cells. In order to prevent old images reappearing in cells that are not fully loaded yet, set the cell's image to nil before loading.

cell.profileImage.image = nil

let myURL = comment.profileImageURL

if comment.profileImageURL != nil {
    [...]
} else {
    cell.profileImage.image = UIImage(named: "Profile Image")
}
5
Ashley Mills On

Each cell you display in image in can be reused if is scrolled off the screen. Using af_setImage will fetch the image, and display it when the response is received, but by that time the cell might be reused, and be trying to display an image at a different URL.

You can handle this in a couple of ways, but first thing to do is to add a Comment property to your cell, and handle the downloading there rather than in the view controller (you might find making all your IBOutlet properties fileprivate helps with this - it makes sure you're can't update the cell's UI from the view controller)

1) Cancel the download request when the cell is reused…

override func prepareForResuse() {
    profileImage.af_cancelImageRequest
}

This should prevent the response for a previous URL being returned, but will mean the image isn't downloaded.

2) Check the URL that that the response is for is the one you're expecting.

var requestedURL: URL?
var comment: Comment? {
    didSet {
        if let myURL = Comment.profileImageURL {
            requestedURL = myURL

            // Fetch image from URL and when response is received, 
            // check self.myURL == response.url

        } else {
            profileImage.image = UIImage(named: "Profile Image")
        }
    }
}

This way the image will still be downloaded even if it's not displayed.