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.
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?
UIImage contains the data for an image. UIImageView is a custom view meant to display the UIImage .
An object that manages image data in your app.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With