Setting images in UITableViewCell in Swift
I have a list of reddit posts that I want to display a thumbnail if it exists. It works for me, but it is very buggy. There are two main problems:
- Resize image on click
- Images move in scrolling
This is the code:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Post", forIndexPath: indexPath) as UITableViewCell
let post = swarm.posts[indexPath.row]
cell.textLabel!.text = post.title
if(post.thumb? != nil && post.thumb! != "self") {
cell.imageView!.image = UIImage(named: "first.imageset")
var image = self.imageCache[post.thumb!]
if(image == nil) {
FetchAsync(url: post.thumb!) { data in // code is at bottom, this just drys things up
if(data? != nil) {
image = UIImage(data: data!)
self.imageCache[post.thumb!] = image
dispatch_async(dispatch_get_main_queue(), {
if let originalCell = tableView.cellForRowAtIndexPath(indexPath) {
originalCell.imageView?.image = image
originalCell.imageView?.frame = CGRectMake(5,5,35,35)
}
})
}
}
} else {
dispatch_async(dispatch_get_main_queue(), {
if let originalCell = tableView.cellForRowAtIndexPath(indexPath) {
originalCell.imageView?.image = image
originalCell.imageView?.frame = CGRectMake(5,5,35,35)
}
})
}
}
return cell
}
This is the app when it loads - it looks like everything is working:
Then if I click on the image (even when scrolling) it changes:
And if you scroll up and down the pictures are getting nasty (look at the middle post - Generics fun):
What am I doing wrong?
** Images and captions are pulled from reddit, not generated by me **
EDIT: FetchAsync class as promised:
class FetchAsync {
var url: String
var callback: (NSData?) -> ()
init(url: String, callback: (NSData?) -> ()) {
self.url = url
self.callback = callback
self.fetch()
}
func fetch() {
var imageRequest: NSURLRequest = NSURLRequest(URL: NSURL(string: self.url)!)
NSURLConnection.sendAsynchronousRequest(imageRequest,
queue: NSOperationQueue.mainQueue(),
completionHandler: { response, data, error in
if(error == nil) {
self.callback(data)
} else {
self.callback(nil)
}
})
callback(nil)
}
}
source to share
Unfortunately, this appears to be a limitation of the General table cell. As a result, I created a custom TableViewCell. Oh relied on Ray Wenderlich's tutorial which can be found here: http://www.raywenderlich.com/68112/video-tutorial-table-views-custom-cells
This is a bit of a bummer since the code is so trivial, but I think on the bright side it means a "simple" solution.
My final code:
PostCell.swift
(all scaffolding)
import UIKit
class PostCell: UITableViewCell {
@IBOutlet weak var thumb: UIImageView!
@IBOutlet weak var title: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
PostsController.swift
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("PostCell", forIndexPath: indexPath) as PostCell
let post = swarm.posts[indexPath.row]
cell.title!.text = post.title
if(post.thumb? != nil && post.thumb! != "self") {
cell.thumb!.image = UIImage(named: "first.imageset")
cell.thumb!.contentMode = .ScaleAspectFit
var image = self.imageCache[post.thumb!]
if(image == nil) {
FetchAsync(url: post.thumb!) { data in
if(data? != nil) {
image = UIImage(data: data!)
self.imageCache[post.thumb!] = image
dispatch_async(dispatch_get_main_queue(), {
if let postCell = tableView.cellForRowAtIndexPath(indexPath) as? PostCell {
postCell.thumb!.image = image
}
})
}
}
} else {
dispatch_async(dispatch_get_main_queue(), {
if let postCell = tableView.cellForRowAtIndexPath(indexPath) as? PostCell {
postCell.thumb!.image = image
}
})
}
}
return cell
}
And my worthless storyboard:
source to share
I'm not sure if this is the best way, but here are some solutions:
-
Use AFNetworking like everyone else. It has the idea of ββan image of the owner of the site, asynchronous loading of the overridden image and smart caching. Install with cocoa pods, create bridge file
#import "UIImageView+AFNetworking.h"
-
Create two different types of cells. Before grabbing a cell with dequeReusableCell ... in your cellForRowAtIndexPath, check if it has expanded. If you expand, go back and fill in an expanded cell, otherwise go back and fill in an unexpanded cell. A cell is usually expanded if it is the "selected" cell.
Your mileage may vary
source to share
It is a huge mistake to call tableView.cellForRowAtIndexPath from the UITableViewDataSource implementation of the tableView (tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell. Instead, when the asynchronous thumb fetch is complete, update the model with the image and then ask the tableView to reload the Rows for that particular indexPath cell. Let your data source define the correct indexPath. If the cell is off-screen when the image finishes loading, there will be no performance impact. And of course reloadRows on the main thread.
source to share