Collection view reusing images while scrolling in swift 2 - ios

I'm simply desperate, searched for 6 hours now and can't seem to find an answer.
I have a collection view with images loaded from a remote server and while scrolling the cells are being refreshed with previous images for a few seconds before settling.
Tried overriding "prepare for reuse" and setting the imageview image to nil but it is still not working..
Would appreciate an example from someone who got it working,
thanks a lot!
EDIT - ADDING THE CODE
CollectionView:
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier("cell", forIndexPath:indexPath) as! PictureCell
cell.pictureCD = GalleryCD().fetchTable()[indexPath.row]
return cell
}
CollectionViewCell:
class PictureCell: UICollectionViewCell {
#IBOutlet weak var picture: UIImageView!
var pictureCD: NSManagedObject! {
didSet { self.setupData() }
}
override func prepareForReuse() {
super.prepareForReuse()
self.picture.image = nil
self.picture.setNeedsDisplay() // tried adding after some recommendations
self.setNeedsDisplay() // tried adding after some recommendations
}
func setupData(){
self.picture.image = UIImage(named: "blank")
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), {
let URL = (NSURL (string: url.pictureSquareGET(picture: self.pictureCD)))!
let data = NSData(contentsOfURL: URL)!
dispatch_async(dispatch_get_main_queue()) {
self.picture.image = UIImage(data: data)
}
})
}
}

You can update the prepareForReuse method
override func prepareForReuse() {
myImageView.image = nil
super.prepareForReuse()
}

Your cells have previous images loaded into them because of cell reuse so you are correct there.
All you should need to do is set the cells image as nil in your cellForItemAtIndexPath BEFORE fetching the updated ones from your server.

Related

Unable to display thumbnails in UICollectionView

I am trying to recreate this thing. I've created in Storyboard skeleton. Here's the idea of my code:
Fetch images from URL's array with help of the function getThumbnailFromImage
Add UIImage's with my thumbnails in array webImages
Add in ViewController reusable cell MyCollectionView
...
But here I am with this))) (Don't mind absence of Auto Layout). What am I doing wrong? I think that the problem is with reloadData() but I don't know where to put it.
ViewController:
//
// ViewController.swift
// youtube-clone
//
// Created by мас on 16.08.2022.
//
import Foundation
import UIKit
import YouTubePlayer
import AVFoundation
class ViewController: UIViewController, UICollectionViewDelegate, UICollectionViewDataSource {
var url: [URL?] = [
URL(string: "https://www.youtube.com/watch?v=KhebpuFBD14"),
URL(string: "https://www.youtube.com/watch?v=UfNdNrRHpUw"),
URL(string: "https://www.youtube.com/watch?v=CX-BdDHW0Ho"),
URL(string: "https://www.youtube.com/watch?v=NIOMtSzfpck")
]
var webImages: [UIImage] = []
var currentPage: Int = 0
#IBOutlet var myPage: UIPageControl!
#IBOutlet weak var buttonInfo: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
setupLayout()
myPage.currentPage = 0
myPage.numberOfPages = webImages.count
}
// MARK: - Collection View Setup
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return webImages.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! MyCollectionCell
getThumbnailFromImage(url: url[indexPath.row]!, completion: { image in
self.webImages.append(image!)
})
cell.myWebImage.image = webImages[indexPath.row]
cell.myWebImage.layer.cornerRadius = 20
return cell
}
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
myPage.currentPage = indexPath.row
}
// MARK: - Layout Setup // IGNORE IT
func setupLayout() {
buttonInfo.layer.cornerRadius = 25
buttonInfo.imageView!.transform = CGAffineTransform(rotationAngle: 180 * .pi / 180)
self.navigationController?.navigationBar.largeTitleTextAttributes = [NSAttributedString.Key.foregroundColor: UIColor.white]
}
// MARK: - Videos Thumbnail Fetcher
func getThumbnailFromImage(url: URL, completion: #escaping ((_ image: UIImage?) -> Void)) {
DispatchQueue.global().async {
let asset = AVAsset(url: url)
let avAssetImageGenerator = AVAssetImageGenerator(asset: asset)
avAssetImageGenerator.appliesPreferredTrackTransform = true
let thumbnailTime = CMTimeMake(value: 7, timescale: 1)
do {
let cgThumbImage = try avAssetImageGenerator.copyCGImage(at: thumbnailTime, actualTime: nil)
let thumbImage = UIImage(cgImage: cgThumbImage)
DispatchQueue.main.async {
completion(thumbImage)
}
}
catch {
print(error.localizedDescription)
}
}
}
}
Reusable Cell AKA MyCollectionCell:
import UIKit
class MyCollectionCell: UICollectionViewCell {
#IBOutlet var myWebImage: UIImageView!
}
P.s.: YouTubePlayer is custom pod from GitHub, it's not currently used.
You do NOT have to use AVAssetImageGenerator, Simply you can use Youtube API to fetch the thumbnail images as .jpg image by video id,
and each YouTube video has four generated images.
https://img.youtube.com/vi/{id}/0.jpg
https://img.youtube.com/vi/{id}/1.jpg
https://img.youtube.com/vi/{id}/2.jpg
https://img.youtube.com/vi/{id}/3.jpg
Example
https://img.youtube.com/vi/KhebpuFBD14/0.jpg
And then it is preferred to use a third party to load this image as its displayed in a list, like https://github.com/SDWebImage/SDWebImage or https://github.com/onevcat/Kingfisher and you will NOT be worry about Concurrency or caching.
A couple of thoughts:
#matt is right in the comment - getThumbnailFromImage will likely not have called the completion block by the time cellForItemAt returns.
From what is visible in the code you posted, webImages.count will still be 0 when your collection view checks numberOfItemsInSection. If the number of items is 0, cellForItemAt may never get called so the call to getThumbnailFromImage wouldn't even be reached. (I'm not sure if the white box in your screenshot is part of a cell or another view element. If a cell is being displayed, I'm assuming you're populating webImages somewhere else before the collection view gets laid out).
One way you could work around these issues is by giving each cell a URL rather than a thumbnail. That way the cell can be displayed while the image is still loading. The cell could look something like this:
class MyCollectionCell: UICollectionViewCell {
#IBOutlet var myWebImage: UIImageView!
func configure(urlString: String) {
guard let self = self, let url = URL(string: urlString) else {
return
}
getThumbnailFromImage(url: url, completion: { [weak self] image in
self?.myWebImage.image = image
})
}
// Move `getThumbnailForImage` function to here, or give the cell a delegate to call back to the VC with if you don't want any networking in the view itself
}
The cellForItemAt function in the VC would need to be changed to something like this:
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! MyCollectionCell
cell.configure(urlString: url[indexPath.row])
cell.myWebImage.layer.cornerRadius = 20 // This should probably live in the cell since the parent doesn't actually need to know about it!
return cell
}
An added benefit of this approach is that you're not referencing a separate array of images that could theoretically end up being in the wrong order if there's a mistake somewhere in the code. You could get rid of the webImages array entirely and use urls.count in numberOfItemsInSection instead - or eventually the number of elements returned from an API somewhere.
Side note - make sure you add [weak self] at the beginning of any closure that references self to avoid trying to access it after it's been deallocated! Currently the call to getThumbnailFromImage doesn't have that :)
Also, note that I changed to a guard statement for checking that the URL exists. This is much safer than force unwrapping a URL(string:) value, especially if you ever end up getting the strings from a dynamic source.

Swift: Update CollectionView Cell Image

Im making a game that has a collectionView character select at the bottom of the GameScene. Many of the characters are locked, so i display a "Locked" image as the collection view cell image. However, when the user unlocks the character and they load the GameScene again(move to gameOver scene, restart the game back to present GameScene), i need to update the collectionView cells images and swap out the "Locked" image with the regular image. Ive tried so many different things but it isn't working. It won't swap out/reload the new images. I know its just something small I'm missing but heres all my code associated with the collection view.
class CharactersCollectionViewCell: UICollectionViewCell {
//MARK : Public API
var characters : NACharacters!{
didSet{
updateUI()
}
}
//MARK : Private
#IBOutlet weak var featuredImageView: UIImageView!
private func updateUI(){
self.layer.borderColor = UIColor.whiteColor().CGColor
self.layer.borderWidth = 2.5
self.layer.masksToBounds = true
self.layer.cornerRadius = CGFloat(roundf(Float(self.frame.size.width/2.0)))
featuredImageView.image = characters.featuredImage
}
}
class NACharacters {
var featuredImage : UIImage!
static var characterLineUp = [NACharacters]()
init(featuredImage: UIImage){
self.featuredImage = featuredImage
}
static func createCharacters() -> [NACharacters]{
print("Inside create character")
characterLineUp.insert(NACharacters(featuredImage: UIImage(named: "DiploSquadCollectionView")!), atIndex: 0)
characterLineUp.insert(NACharacters(featuredImage: UIImage(named: "LockedNBAPlayersCollectionView")!), atIndex: 1)
if(NSUserDefaults().unlockNBAPlayers == true){
characterLineUp[1].featuredImage = UIImage(named: "NBAPlayersCollectionView")!
}
characterLineUp.insert(NACharacters(featuredImage: UIImage(named: "LockedLeanSquadCollectionView")!), atIndex: 2)
if(NSUserDefaults().totalDabs >= 10000){
print("setting up lean to be unlocked")
characterLineUp[2].featuredImage = UIImage(named: "LeanSquadCollectionView")!
}
return characterLineUp
}
}
//Inside Game View Controller
#IBOutlet var collectionView: UICollectionView!
//MARK : Data Source
internal var characters = NACharacters.createCharacters()
extension GameViewController : UICollectionViewDataSource, UICollectionViewDelegate{
func numberOfSectionsInCollectionView(collectionView: UICollectionView) -> Int {
return 1
}
func collectionView(collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return characters.count
}
func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(Storyboard.CellIdentifier, forIndexPath: indexPath) as! CharactersCollectionViewCell
cell.characters = self.characters[indexPath.item]
return cell
}
//Inside Game Scene
var collectionView: UICollectionView? {
return self.view?.subviews.filter{ $0 is UICollectionView }.first as? UICollectionView
}
override func didMoveToView(view: SKView) {
//INSIDE HERE I NEED TO UPDATE THE COLLECTION VIEW CELL IMAGES
}
I know this looks like a lot of code but most of it is pretty standard. I show you all of this so that we can pin point exactly whats wrong or what needs to be done to get this to work. This is all of my code associated with the collectionView.
Have you try this in same place ::
imageView.image = nil
then set the image you want to update as ..
imageView.image = UIImage(named : "YourImage")
When you need to 'refresh' a Collection View, tell it to reload its data, then all the images should update as needed.
collectionView.reloadData()

Custom UIImageView realigning on touch of UITableViewCell

I have seen this issue a lot of places but have yet to come across a solution that works for me. I have a custom UITableViewCell, in which I have placed a UIImageView. The Image view is supposed to hug the right side of the cell (with constraints from an xib file). Here is the code for how the cell is created and then formatted:
class PlaylistCell: UITableViewCell {
#IBOutlet var imView: UIImageView?
#IBOutlet var label: UILabel?
var playlist:SPTPartialPlaylist? {
didSet {
self.configure()
}
}
func configure()
{
self.imView?.clipsToBounds = true
self.label?.text = self.playlist?.name
let uri = (self.playlist?.images[0] as! SPTImage).imageURL
dispatch_async(dispatch_get_main_queue(), {
let data = NSData(contentsOfURL: uri!)
if (data != nil) {
self.imView?.image = UIImage(data: data!)
self.layoutSubviews()
}
})
}
And in my ViewController that has the table view in it:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCellWithIdentifier("PlaylistCell") as! PlaylistCell
cell.playlist = self.playlists[indexPath.row]
cell.imView?.image = UIImage(named: "placeholder")
return cell
}
Everything loads correctly and the cells look fine, however when one of the cells is touched, the image snaps to the left side of the cell and decreases in size. Does anyone know why this might be happening? (PS I have tried using SDWebImage and the same issue ensues)
Can you try to do add that in your PlayListCell?
override func didMoveToSuperview() {
self.layoutIfNeeded()
}

UITableViewCell image not shown until selected

My UITableViewCells images are displaying until I scroll back upwards whereby the images would not be displayed until the cell is selected.
The same problem also happens when I switch from another ViewController to the initial ViewController*(which contains the image)*
I have checked that the imgURL of the image is correct.
Libraries used are: AFNetworking for the image
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("FeedCell", forIndexPath: indexPath) as! MyCell
cell.itemImageView.image = nil
self.configureCell(cell, atIndexPath: indexPath)
return cell
}
// AFNetworking download and display image
func uploadIMG(cell:MyCell,imgURL:NSURL,placeholderIMG:String,atIndexPath indexPath: NSIndexPath) {
var imageRequest: NSURLRequest = NSURLRequest(URL: imgURL)
cell.itemImageView!.setImageWithURLRequest(imageRequest, placeholderImage: UIImage(contentsOfFile: "logo.png"), success: { [weak cell] request,response,image in
if (cell != nil) {
cell!.itemImageView.image = image
}}
, failure: nil)
}
// called from cellForRowAtIndexPath, retrieve img url to update image
func configureCell(cell: MyCell, atIndexPath indexPath: NSIndexPath) {
let item = self.items[indexPath.row] as MWFeedItem
var URLofImage: NSURL = NSURL(string: item.link)!
var session = NSURLSession.sharedSession()
let task = session.dataTaskWithURL(URLofImage, completionHandler: {(data,response, error) in
let text = NSString(data: data, encoding: NSUTF8StringEncoding)
var home = HTMLDocument(data: data, contentTypeHeader: text as! String)
var div = home.nodesMatchingSelector("img")
var urlString = div[1].firstNodeMatchingSelector("img")
let urlData = (urlString as HTMLElement).firstNodeMatchingSelector("img")
var urlFinal = urlData.attributes["src"]! as! String
if urlFinal != "/images/system/bookmark-shorturl.png" {
// call updateIMG function
self.uploadIMG(cell, imgURL: NSURL(string: "http:www.animenewsnetwork.com" + urlFinal)!, placeholderIMG: "logo.png",atIndexPath: indexPath)
}
})
Image representation of the problem (Initial image working fine)
Second Image (I scrolled downwards and then scrolled upwards, Image not showing)
I select some cells and the images for those cells will then appear
Try after setting image into cell, update that cell in table view by calling method tableView:reloadRowsAtIndexPaths:withRowAnimation. Or write your custom cell with custom image view. And please, do not forgot that image setting code must run in main thread.
The problem was that my Image wasn't set on the main thread. To solve the problem, I simply used the following code below which ensured that my image will be set immediately.
dispatch_async(dispatch_get_main_queue(), {
// do image functions here
)}
Misread the Question, but keeping this in case anyone has a similar problem, but with autolayout.
I believe you are using autolayout. So if the imageView's frame size is using the intrinsic content size, the size of it's image, it'll be CGSizeZero when there is no image. There is no image when the cell is first displayed, because it needs to be downloaded. So then the image is downloaded and gets assigned to imageView.image. This does not automatically invalidate the layout. You'll need to do that so the imageView frame gets recalculated based on the size of the image. The reason it shows up after scrolling away and scrolling back or selecting it is because the image has been downloaded in that time and the cells layout is recalculated when it gets displayed again or selected.
Below is my TestCell and TestViewController
import UIKit
import AFNetworking
class TestCell : UITableViewCell {
static let cellIdentifier = "TestCell"
#IBOutlet var downloadedImageView: UIImageView!
#IBOutlet var rowLabel: UILabel!
#IBOutlet var statusLabel: UILabel!
}
class TestTableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
tableView.rowHeight = 100
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 30;
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier(TestCell.cellIdentifier, forIndexPath: indexPath) as! TestCell
let randomName = "\(Random.firstName().lowercaseString).\(Random.lastName().lowercaseString)"
let randomImageURL = NSURL(string: Random.avatarImageURL(name: randomName))!
cell.rowLabel.text = String(indexPath.row)
cell.statusLabel.text = "Not Downloaded"
var imageRequest: NSURLRequest = NSURLRequest(URL: randomImageURL)
cell.downloadedImageView.setImageWithURLRequest(imageRequest, placeholderImage: UIImage(named: "placeholder.png"),
success: { [weak cell]
(request, response, image) in
if let cell = cell {
cell.downloadedImageView.image = image
cell.rowLabel.text = String(indexPath.row)
cell.statusLabel.text = "Downloaded"
}
},
failure: { [weak cell]
(request, response, error) in
if let cell = cell {
cell.downloadedImageView.image = nil
cell.rowLabel.text = String(indexPath.row)
cell.statusLabel.text = "Failed: \(error.localizedDescription)"
}
})
return cell
}
}
//
// Random.swift
import Foundation
class Random {
static let firstNames = ["Tora", "Shasta", "Camelia", "Gertrudis", "Charita", "Donita", "Debbra", "Shaquana", "Tommy", "Shara", "Ignacia", "Cassondra", "Melynda", "Lisette", "Herman", "Rhoda", "Farah", "Tim", "Tonette", "Johnathon", "Debroah", "Britni", "Charolette", "Kyoko", "Eura", "Nevada", "Lasandra", "Alpha", "Mirella", "Kristel", "Yolande", "Nelle", "Kiley", "Liberty", "Jettie", "Zoe", "Isobel", "Sheryl", "Emerita", "Hildegarde", "Launa", "Tanesha", "Pearlie", "Julianna", "Toi", "Terina", "Collin", "Shamika", "Suzette", "Tad"]
static let lastNames = ["Austen", "Kenton", "Blomker", "Demars", "Bibbs", "Eoff", "Alcantara", "Swade", "Klinefelter", "Riese", "Smades", "Fryson", "Altobelli", "Deleeuw", "Beckner", "Valone", "Tarbox", "Shumate", "Tabone", "Kellam", "Dibiase", "Fasick", "Curington", "Holbrook", "Sulzer", "Bearden", "Siren", "Kennedy", "Dulak", "Segers", "Roark", "Mauck", "Horsman", "Montreuil", "Leyva", "Veltz", "Roldan", "Denlinger", "James", "Oriley", "Cistrunk", "Rhodes", "Mcginness", "Gallop", "Constantine", "Niece", "Sabine", "Vegter", "Sarnicola", "Towler"]
class func int(#min: Int, max: Int) -> Int {
return Int(arc4random_uniform(UInt32(max-min))) + min //???: RTFM on arc4random, might be need (max+1)-min.
}
class func int(#range: Range<Int>) -> Int {
return int(min: range.startIndex, max: range.endIndex)
}
class func selectElement<T>(#array: [T]) -> T {
return array[int(range: 0..<array.count)]
}
class func firstName() -> String {
return Random.selectElement(array: Random.firstNames)
}
class func lastName() -> String {
return Random.selectElement(array: Random.lastNames)
}
class func avatarImageURL(var name: String? = nil) -> String {
if name == nil {
name = "(Random.firstName().lowercaseString).Random.lastName().lowercaseString"
}
let avatarImageSize = Random.int(min: 40, max: 285)
return "http://api.adorable.io/avatars/\(avatarImageSize)/\(name!)#gmail.png"
}
class func imageURL() -> String {
let imageWidth = Random.int(min:120, max:1080)
let imageHeight = Random.int(min:120, max:1080)
return "http://lorempixel.com/g/\(imageWidth)/\(imageHeight)/"
}
}
When you scroll, cell will reload. (you reload to redownload your image) -> it's problem.
Solved:
You create array for save image data after download.
And cell get image from this array, not redownload
Hope this helpful!

Setting images in UITableViewCell in Swift

I have a list of reddit posts that I want to display the thumbnail of, if it exists. I have it functioning, but it's very buggy. There are 2 main issues:
Images resize on tap
Images shuffle on scroll
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 up - looks like everything is working:
Then if I tap on an image (even when you scroll) it resizes:
And if you scroll up and down, the pictures get all screwy (look at the middle post - Generics fun):
What am I doing wrong?
** Pictures and Titles 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)
}
}
Unfortunately, this seems to be a limitation of the "Basic" table view cell. What I ended up doing was creating a custom TableViewCell. A relied on a tutorial by Ray Wenderlich that can be found here: http://www.raywenderlich.com/68112/video-tutorial-table-views-custom-cells
It's a bit of a bummer since the code is so trivial, but I guess on the bright side that means it's a 'simple' solution.
My final code:
PostCell.swift (all scaffolded code)
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 measly storyboard:
I'm not sure the best way to do this, but here a couple of solutions:
Use AFNetworking, like everyone else does. It has the idea of a place holder image, async downloading of the replacement image, and smart caching. Install using cocoa pods, make a bridging file with #import "UIImageView+AFNetworking.h"
Create two different types of cells. Before grabbing a cell with dequeReusableCell... in your cellForRowAtIndexPath, check if it's expanded. If expanded, return and populate an expanded cell otherwise return and populated an unexpanded cell. The cell is usually expanded if it is the 'selected' cell.
Your mileage may vary
it is a huge mistake to call tableView.cellForRowAtIndexPath from within UITableViewDataSource's implementation of tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell. Instead when the async fetch of the thumb image is completed, update your model with the image, then request tableView to reloadRows for that specific cell's indexPath. Let your data source determine the correct indexPath. If the cell is offscreen by the time the image download is complete there will be no performance impact. And of course reloadRows on the main thread.

Resources