I have searched through the internet and found solutions like, to use prepareForReuse() and making the imageview = nil at the cellForRowAt. I have tried both these methods but it does not work. Please help me to figure out how to prevent image duplication in UITableViewCell
Below are the code that I have tried previously,
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: String(describing: EmployeeTableViewCell.self), for: indexPath) as! EmployeeTableViewCell
cell.onBindCell(employee: directoryPresenter!.getEmployeeItem(position: indexPath.row))
directoryPresenter!.getProfileImage(id: (directoryPresenter!.getEmployeeItem(position: indexPath.row).employeeID)) { image in
if let image = image {
cell.profileImage.image = image
} else {
// Default image or nil
cell.profileImage.image = UIImage(named: "placeholder_profile_male")
}
}
cell.actionCallBack = self
return cell
}
Below is the code that I have added inside the custom table view cell class,
#IBOutlet weak var profileImage: UIImageView!
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
}
override func prepareForReuse() {
super.prepareForReuse()
self.profileImage.image = nil
}
Your image fetched from closure, you should set its default image before block callback, like this:
// Default image
cell.profileImage.image = UIImage(named: "placeholder_profile_male")
// fetch employee image
directoryPresenter!.getProfileImage(id: (directoryPresenter!.getEmployeeItem(position: indexPath.row).employeeID)) { image in
// handle async fetching task
// call main queue to update UI
DispatchQueue.main.async {
if let image = image {
cell.profileImage.image = image
}
}
}
If your image is fetched from url, here is the idea to let URLSession handling the job:
cell.profileImage.image = UIImage(named: "placeholder_profile_male")
let url = URL(string: urlString)!
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
print("Error: \(error)")
return
}
let image = UIImage.init(data: data!)
DispatchQueue.main.async {
cell.profileImage.image = image
}
}.resume()
Best way to use image and placeholder. Use SDWebImage
Steps
instal pod pod 'SDWebImage'
After successfully istallation. Open your table view controller class.
import SDWebImage
In Cell for row at index path
// this is a activity indicator and automatically work on your image
cell.yourImageName.sd_imageIndicator = SDWebImageActivityIndicator.grayLarge
cell.yourImageName.sd_setImage(with: URL(string: yourImageInString),
placeholderImage: UIImage(named: "yourPlaceHolderImageName"))
If a UITableViewCell object has a reuse identifier, the table view invokes this method just before returning the object from the UITableView method dequeueReusableCell(withIdentifier:). To avoid potential performance issues, you should only reset attributes of the cell that are not related to content, for example, alpha, editing, and selection state. The table view's delegate in tableView(_:cellForRowAt:) should always reset all content when reusing a cell.
The table view doesn’t call this method if the cell object doesn’t have an associated reuse identifier, or if you use reconfigureRows(at:) to update the contents of an existing cell.
If you override this method, you must be sure to invoke the superclass implementation.
Visit https://developer.apple.com/documentation/uikit/uitableviewcell/1623223-prepareforreuse
try this as well
cell.profileImage.image = nil
directoryPresenter!.getProfileImage(id: (directoryPresenter!.getEmployeeItem(position: indexPath.row).employeeID)) { image in
if let image = image {
cell.profileImage.image = image
} else {
// Default image or nil
cell.profileImage.image = UIImage(named: "placeholder_profile_male")
}
}
So I have been reading lots of solutions to this problem and it seems no matter what I do, I am still getting jerky scrolling in my UITableView when there are images present in my cells.
Here is a little info on how I am generating my cells.
I am calculating heights for the cells and cacheing the heights in height for row at index path
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if let height = cachedHeights[indexPath.row] {
return height
} else {
let post = dataSource.items[indexPath.row]
var CellClass = FeedTableViewCell.self
if let RegisteredCellClass = cells[post.reusableIdentifier] {
CellClass = RegisteredCellClass
}
cachedHeights[indexPath.row] = CellClass.height(post)
return CellClass.height(post)
}
}
I have verified that the actual and calculated sizes are the same.
When configuring the cell in cell for row at indexpath, I setup all the elements and load the images asynchronously with SDWebImage
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if indexPath.section == 1 {
let post = dataSource.items[indexPath.row]
var wallCell: FeedTableViewCell?
if let registeredCell = tableView.dequeueReusableCell(withIdentifier: post.reusableIdentifier) as? FeedTableViewCell {
wallCell = registeredCell
if let postCell = wallCell as? FeedTableViewPostCell {
postCell.informationDelegete = self
postCell.actionDelegate = self
}
}
guard let cell = wallCell else { return FeedTableViewPostCell() }
cell.configureCell(post)
cell.delegate = self
return cell
} else {
return UITableViewCell()
}
}
Configure cell calls the method on the cell that populates the element with the post data. There is a sub view called mediaview that handles the images. If there are images int he post they are configured in that view like so.
for (index, element) in media.enumerated() where index < 3 {
addSubview(viewsArray[index])
viewsArray[index].setImage(with: element.source, placeholderImage: nil)
}
I read something about SDWebImage causing issues in it's default UIImageView extension so I wrote my own and this is the code for that.
func setImage(with url: URL?, placeholderImage: UIImage?){
if let placeholder = placeholderImage{
DispatchQueue.main.async(execute: {
self.image = placeholder
})
}
SDWebImageManager.shared().loadImage(with: url, options: [SDWebImageOptions.cacheMemoryOnly, SDWebImageOptions.scaleDownLargeImages], progress: nil, completed: {(image, data, error, cacheType, finished, url) in
if finished {
DispatchQueue.main.async(execute: {
self.alpha = 0
UIView.transition(with: self, duration: 0.1, options: UIViewAnimationOptions.transitionCrossDissolve, animations: { () -> Void in
self.image = image
self.alpha = 1
}, completion: nil)
})
}
})
}
If I comment out the block in mediaview that sets the image, my scrolling is perfectly smooth so I know it's not another portion of the cell generation. My understanding was that the asynchronous loading should alleviate the scrolling lag but I have attempted just about everything to no avail. Any help or insights on this would be greatly appreciated.
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()
}
I have just started using swift. I am using blocks and NSOperationQueue to download the image in tableViewCell and in the completion handler I am returning the downloaded image. I am trying to update the cell as below.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell = tableView.dequeueReusableCellWithIdentifier("itemCell", forIndexPath: indexPath) as! UITableViewCell
var itemImage = cell.viewWithTag(1000) as! UIImageView
var itemName = cell.viewWithTag(1001) as! UILabel
if let item = self.itemArray?[indexPath.row] {
itemImage.image = UIImage(named: "Placeholder.jpg")
getImageForItem(item, withCompletion: { (image) -> () in
if let cellToUpdate = tableView.cellForRowAtIndexPath(indexPath) {
var imageViewToUpdate = cellToUpdate.viewWithTag(1000) as! UIImageView
imageViewToUpdate.image = image
}
})
itemName.text = item.itemName
}
return cell
}
func getImageForItem(item: item, withCompletion completion:((image: UIImage) -> ())) {
if let image = self.imageCache.objectForKey(item.itemID) as? UIImage {
completion(image: image)
} else {
let request = item.getItemImage(ItemImageSize(rawValue: 2)!, withWidth: 100, shouldFetch: false, block: { (image, tempID) -> Void in
if image != nil {
self.imageCache.setObject(image, forKey: item.itemID)
if item.itemID == tempID {
completion(image: image)
}
}
})
if request != nil {
imageQueue.addOperation(request)
}
}
}
The problem I face is, I am getting the image successfully in the completion block of cellForRowAtIndexPath(), but, I fail to update the cell. For the above code, the downloaded image is applied to all the visible cells in the tableView, but, as I scroll down, I see only the placeholder image. Even I loose the loaded images to placeholder image on scrolling back.
On debugging, I found that
if let cellToUpdate = tableView.cellForRowAtIndexPath(indexPath) {
var imageViewToUpdate = cellToUpdate.viewWithTag(1000) as! UIImageView
imageViewToUpdate.image = image
}
loop is called for the visible cells only first time. But not called again on scrolling. What am I missing?
I sorted out myself. I added one more argument, indexPath to track it.
getImageForItem(item, indexPath: indexPath, withCompletion: { (image, imageIndexPath) -> () in
if Set<NSIndexPath>(tableView.indexPathsForVisibleRows() as! [NSIndexPath]).contains(imageIndexPath) {
itemImage.image=image
}
})
That gave me the perfect solution
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!