Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UIImageView does not initially display image in UITableViewCell

I have a UITableView that loads images from a service. It fetches URLs and downloads the images with NSURLSession.sharedSession().dataTaskWithURL:

NSURLSession.sharedSession().dataTaskWithURL(url, completionHandler: { (data, _, error) -> Void in
                            guard
                                let data = data where error == nil,
                                let image = UIImage(data: data)
                                else {
                                    print(error?.localizedDescription)
                                    return
                            }
                            dispatch_async(dispatch_get_main_queue()) {
                                [unowned self] in
                                self.pictureView.image = image
                                self.pictureView.layoutIfNeeded()
                            }
                        }).resume()

The UIImageView is an outlet to a storyboard scene. The UIImageView content mode is Aspect Fit.

The UIImageView for visible cells initially displays empty (no image) even though I've confirmed an image was set. It isn't until the cell is scrolled off/on screen that the cell will redraw(?) and display the image.

So this seems like some kind of timing issue, but I can't figure it out. I've tried calling all of the following within the dispatch_async block after the image is set:

self.pictureView.setNeedsDisplay()
self.pictureView.setNeedsUpdateConstraints()
self.pictureView.setNeedsLayout()
self.pictureView.reloadInputViews()

None of these fix the problem. What is causing this image to only display after the cell is scrolled out/into view?

EDIT: It may be important to note that this is taking place inside a nib and the associated class. The nib is loaded, then I kick off the download. I'm wondering if the nib lays itself out, and by the time my image is downloaded and set on the UIImageView the layout pass has completed. Then by moving the cell (containing the nib) off/on screen the cell performs a layout again and everything is displayed properly. Just a guess, and I'm still stuck on this.

EDIT 2: Further clarification of the process:

1) My UIViewController contains a UITableView.

2) This UITableView loads cells from a custom UITableViewCell class called PostCell.

3) The entire contents of PostCell are a single view loaded from a nib called PostView (with a PostView class). That load occurs during init:style:reuseIdentifier in PostCell:

postView = NSBundle.mainBundle().loadNibNamed("PostView", owner: self, options: nil).first as? PostView

4) I then set a Post object on postView:

postView.post = Post

Note that this occurs AFTER awakeFromNib() is called on PostView.

5) The post property on PostView has a didSet observer which kicks off the image download based on a URL in the post object (code above). The image is downloaded and set on the UIImageView named pictureView.

Edit 3:

Here is the code for tableView(_:cellForRowAtIndexPath:):

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

    var cell = tableView.dequeueReusableCellWithIdentifier(postCellIdentifier) as? PostCell

        if cell == nil {
           cell = PostCell()
        }

        if let postCell = cell,
            let postArray = postArray,
            let post = postArray[indexPath.row] {
            postCell.post = post
            return postCell
        }

        // This section is hit during a refresh.
        let emptyCell = UITableViewCell()
        emptyCell.contentView.backgroundColor = .whiteColor()
        return emptyCell

    }

Edit 4:

I've found that if I "prime" the UIImageView by pre-setting a placeholder image in the nib, then load my image into that UIImageView, the image will display at the height of the placeholder. When I pull that cell offscreen then on again, the UIImageView will paint the image at the correct size using the constraints setup in the nib.

So it seems the constraints and size of the UIImageView have been set by the time the image loads, and pulling it off/on screen causes a redraw of some kind that resizes the image properly. If I could programmatically trigger that redraw it would help, but see above for what I've already tried.

Edit 6:

Here it is in action. Note that I've set a placeholder image in the nib. This seems to cause the image to show when initially loaded, but at the dimensions of the placeholder. Scrolling it offscreen briefly causes the resizing, when it then displays correctly. enter image description here

Edit 7:

I use PostView nib elsewhere in my app with no problem. When it loads the image is sized correctly. The problem only seems to present itself with the nib is embedded within a UITableViewCell.

Edit 8:

I found the root cause of this issue, called in viewDidLoad of my UIViewController:

tableView.estimatedRowHeight = 500
tableView.rowHeight = UITableViewAutomaticDimension

Using automatic dimension causes the initial incorrect row height. For some reason, scrolling the cell out/into view will recalculate the row height correctly and the image is displayed at the right height. Removing this code will display the image, but the row heights don't benefit from the auto height calculation. What is the best method to auto-calculate row heights and still avoid this problem?

like image 835
Fook Avatar asked Jun 27 '16 16:06

Fook


People also ask

What is the difference between a UIImage and a UIImageView?

UIImage contains the data for an image. UIImageView is a custom view meant to display the UIImage .

What is UIImage?

An object that manages image data in your app.


1 Answers

you can try this extension:

extension UIImageView {
    func downloadedFrom(link link:String, contentMode mode: UIViewContentMode) {
        guard
            let url = NSURL(string: link)
            else {return}
        contentMode = mode
        NSURLSession.sharedSession().dataTaskWithURL(url, completionHandler: { (data, response, error) -> Void in
            guard
                let httpURLResponse = response as? NSHTTPURLResponse where httpURLResponse.statusCode == 200,
                let mimeType = response?.MIMEType where mimeType.hasPrefix("image"),
                let data = data where error == nil,
                let image = UIImage(data: data)
                else { return }
            dispatch_async(dispatch_get_main_queue()) { () -> Void in
                self.image = image
            }
        }).resume()
    }
}

And then you can put this line wherever you want

cell.cellPhoto.downloadedFrom(link: "customImageUrl", contentMode: .ScaleAspectFit) //Try changing contentMode
like image 166
0ndre_ Avatar answered Sep 27 '22 00:09

0ndre_