Repeating images when scrolling in UITableView Swift - ios

EDITED CODE FROM BELOW ANSWER
WHAT I WANT
The images are correct size and show in the correct positions
WHAT I GET
A bunch of images showing up everywhere randomly when I scroll
I have looked through so many answers and I still haven't found a solution to my problem. It seems quite often it is a different problem for each person, and it doesn't help that most questions here are in Objective-C. I find it difficult converting from one to another.
I will just show my table view functions and explain my code and maybe somebody can identify the problem or help me to identify to solve it myself.
-----------------------------------------------------------------------------------
Explanation
Not all News Items (cells) contain pictures. So as you can see (in the first function below) I loop through the sections and the nested cells and if the news item contains a picture if newslists[i][j] != "None, I display the image. From this point it is familiar code, seen from the tutorial here
In the second function I once again loop through the sections and the cells till I land on one that should contain an image. I then change the height of this cell so the image will fit inside.
That is pretty much it...
any ideas?
-----------------------------------------------------------------------------------
CellForRowAtIndexPath
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("NewsCell", forIndexPath: indexPath) as! UITableViewCell
if newslists[indexPath.section][indexPath.row].imageURL != "None"{
let urlString = newslists[indexPath.section][indexPath.row].imageURL
let imgURL = NSURL(string: urlString)
cell.imageView?.image = UIImage(named: "first") // placeholder
if let img = imageCache[urlString] {
cell.imageView?.image = img
println("loaded from cache")
}
else {
let request: NSURLRequest = NSURLRequest(URL: imgURL!)
let mainQueue = NSOperationQueue.mainQueue()
NSURLConnection.sendAsynchronousRequest(request, queue: mainQueue, completionHandler: { (response, data, error) -> Void in
if error == nil {
let image = UIImage(data: data)
self.imageCache[urlString] = image
dispatch_async(dispatch_get_main_queue(), {
if let cellToUpdate = tableView.cellForRowAtIndexPath(indexPath) {
cellToUpdate.imageView?.image = image
println("succesfully downloaded image")
println("size of cache is \(self.imageCache.count)")
}
})
}
else {
println("Error: \(error.localizedDescription)")
}
})
}
}
cell.backgroundColor = UIColor(red: 52/255, green: 138/255, blue: 169/255, alpha: 1.0)
return cell
}
I have one more function that may be causing problems but I doubt it:
HeightForRowAtIndexPath
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if newslists[indexPath.section][indexPath.row].imageURL != "None"{
println("Expanding cell for \(indexPath.section) \(indexPath.row)")
return 190.0
}
return 70.0
}

I'm not sure why you have a double for loop in both method. There are called for every indexPath possible so you can just get you appropriate data with the indexPath section and row.
You can try this code, i don't see any reason for it to not work.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("NewsCell", forIndexPath: indexPath) as! UITableViewCell
cell.imageView?.image = nil
if newslists[indexPath.section][indexPath.row].imageURL != "None"{
let urlString = newslists[indexPath.section][indexPath.row].imageURL
let imgURL = NSURL(string: urlString)
cell.imageView?.image = UIImage(named: "first") // placeholder
if let img = imageCache[urlString] {
cell.imageView?.image = img
println("loaded from cache")
}
else {
...
}
}
return cell
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if newslists[indexPath.section][indexPath.row].imageURL != "None"{
println("Expanding cell for \(i) \(j)")
return 190.0
}
return 70.0
}

All we need to do is use the prepareForReuse() function. As discussed in this medium article, This function is called before cell reuse, letting you cancel current requests and perform a reset.
override func prepareForReuse() {
super.prepareForReuse()
imageView.image = nil
}

Related

Cell highlight shows default image instead of actual image

I've an avatar image in cells in a table. When I touch a cell (highlight) it shows default avatar image instead of the actual image. I'm not sure what is causing this and how to fix. Any help?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell", for: indexPath) as? TableViewCell else {
fatalError("Can't find cell")
}
//...
cell.selectionStyle = .default
self.configCell(cell: cell, indexPath: indexPath)
//...
}
func configCell(cell: TableViewCell, indexPath: IndexPath) {
//...
// Avatar
if let url = URL(string: urlString) {
cell.avatarImageView.image = .defaultImage
cell.tag = indexPath.row
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard let data = data, error == nil else { return }
DispatchQueue.main.async {
if cell.tag == indexPath.row {
cell.avatarImageView.image = UIImage(data: data)
}
}
}
task.resume()
}
//...
}
If you have an issue only with proper image displaying, I'd suggest working with highlightedImage property of UIImageView.
UITableViewCell has .highlighted property (when the cell is pressed down). Thus, if the cell contains UIImageView inside, then when you select/highlight the cell, the UIImageView will use the .highlightedImage instead of just .image.
So just as a backup and fix to the problem, you could additionally provide/tell UIImageView to display an avatar even when it is highlighted.

Jerky scrolling with Images in UITableView

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.

IOS Swift how can hide the space of an Image inside a TableView

I have a tableView that contains a UIImageView and if the Image has a URL then the image displays and if not then no Image is displayed. The issue I have is that if there is no Image then a big blank spot occurs in the TableView as if there was an image. Is there a way to reduce the blank spot or hide it (Images below) ? The image is the big UIImageView in the center . This is my code when it comes to the Images
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "MyFeed", for: indexPath) as! MyFeed
if stream_image_string[indexPath.row].characters.count > 2 {
let strCellImageURL = self.stream_image_string[indexPath.row]
let imgURL: NSURL = NSURL(string: strCellImageURL)!
let request:NSURLRequest = NSURLRequest(url: imgURL as URL)
let config = URLSessionConfiguration.default
let session = URLSession(configuration: config)
let task = session.dataTask(with: request as URLRequest, completionHandler: {(data, response, error) in
DispatchQueue.main.async(execute: { () -> Void in
cell.post_image.image = UIImage(data: data!)
})
});
task.resume()
} else {
cell.post_image!.isHidden = true
cell.post_image!.image = nil
}
return cell
}
Essentially if the String coming back has 2 or more characters then it's a valid URL and the image is downloaded; the part that I am focused on is the else statement and this code
else {
cell.post_image!.isHidden = true
cell.post_image!.image = nil
}
So obviously if it goes in the else statement then there is no image and I set the Image to null or nil then I try to hide the extra white space by setting the Image to hidden however that does not work . Any idea on how I can hide the white space ? I have also been reading this question but it does not work iOS swift imageView cannot be hidden in TableViewCell
Give outlet of image's width and if there is no image then set constant of that outlet to "0".
e.g.
if(!image)
{
widthOfImage.constant = 0
}
I experienced the same problem myself. You need to create 2 cells for this. Like this:
override func tableView (_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
if (stream_image_string[indexPath.row] == "")
{
let cell = tableView.dequeueReusableCell (withIdentifier: "noImageMyFeed", for: indexPath) as! noImageMyFeed
return cell
}
else
{
let cell = tableView.dequeueReusableCell (withIdentifier: "MyFeed", for: indexPath) as! MyFeed
return cell
}
}
this video will help you in detail : https://www.youtube.com/watch?v=FAxtWtqeMIM
adapt the video to its own content, create a cell from scratch by simply deleting the image part

Fatal error. Array index out of range in indexpath - swift

My following code is part of a search controller. It works without a problem but after 8-10 searches, I encounter fatal error (Thread 1 : EXC_BAD_INSTRUCTION) on following line :
let movie = filteredMovies[indexPath.item]
Could you please advise the way of solving this kind of problems.
extension searchResults: UITableViewDataSource, UITableViewDelegate {
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("searchCell", forIndexPath: indexPath) as! searchResultCell
let movie = filteredMovies[indexPath.item]
cell.searchLabel.text = movie.title
let fileUrl = NSURL(string: movie.thumb)!
if let data = NSData(contentsOfURL: fileUrl)
{
cell.searchImage.contentMode = UIViewContentMode.ScaleAspectFit
cell.searchImage.image = UIImage(data: data)
// to make images rounded
cell.searchImage.backgroundColor = UIColor.clearColor()
cell.searchImage.layer.cornerRadius = 15.0
cell.searchImage.clipsToBounds = true
cell.backgroundColor = UIColor.clearColor()
}
return cell
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int
{
return filteredMovies.count
}
}
You can check if indexPath.item is within the bounds of the array filteredMovies:
if (indexPath.item >= 0 || indexPath.item < movie.count) {
// now you can safely use filteredMovies[indexPath.item]
} else {
// print something so you can investigate
}
And if you wonder why indexPath.item might be out of the bounds of filteredMovies, it's other programming logic you have to investigate (maybe you remove some elements in filteredMovies after the tableView is loaded).
After all, always checking the bounds is a good thing when you have doubt or want to make sure it will never cause crash (fault tolerance concept).

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!

Resources