How to reload UIImage loaded asynchronously with SDWebImage in UITableView cell - ios

The problem is that I don't know how to refresh images loaded asynchronously from url with SDWebImage.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)
let item = items[indexPath.row]
if let url = URL(string: item.stringUrl) {
let placeholderImage = UIImage(named: "noPhoto.png")
itemImage.sd_setImage(with: url, placeholderImage: placeholderImage)
}
cell.imageView?.image = itemImage.image
cell.textLabel?.text = item.title
cell.detailTextLabel?.text = "item id: \(item.id)"
return cell
}
The images will only refresh after scrolling through the table. Until then the table only shows placeHolder image which is expected as it only reloads images when cells are reused. I tried to use the completion handler to refresh images when loaded using tableview.reload() as shown below:
itemImage.sd_setImage(with: url, placeholderImage: placeholderImage, completed: {image,error,cache,url in
if image != nil {
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
})
This solution results in tableView loading all the images in a second which is what I want but it reloads the whole table basically every time I try to scroll through it so it "pushes" the whole table to the top position instantly.
The last solution I tries is this:
itemImage.sd_setImage(with: url, placeholderImage: placeholderImage, completed: {image,error,cache,url in
if image != nil {
DispatchQueue.main.async {
self.tableView.reloadRows(at: [indexPath], with: .none)
}
}
})
This also doesn't and makes tableview randomly showing and hiding images.
I suppose the problem is that I try to reload images in a function that creates cells but I can't find a solution to how resolve this issue. I will be very grateful for any help.

Related

UITableView not showing remote image until scroll or reload of tableviewcell

I am using Kingfisher to download and cache remote images.
I would like to render those images in a UITableViewCell
Currently the images do not show until I scroll.
I suspect this is because the initial image size is nil, once I scroll the cell is redrawn and the image is rendered.
To negate this issue I have called tableView.reloadRows(at: [indexPath], with: .none) in the completion handler of Kingfisher
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "FeedTableViewCellId", for: indexPath) as! FeedGifCell
let url = URL(string: items[indexPath.item])!
let imageView = ImageResource(downloadURL: url, cacheKey: "\(url)-imageview")
let placeHolderImage = UIImage.from(hex: "ffffff").resize(width: 1, height: 190)
cell.gifImage.kf.setImage(with: imageView, placeholder: placeHolderImage) { _ in
tableView.reloadRows(at: [indexPath], with: .none)
}
return cell
}
I have applied a simple placeholder of a fixed height and width to give the cell some height before the image is applied.
I am not sure this is the best approach, I do not like calling tableView.reloadRows(at: [indexPath], with: .none) this feels a little.....hacky.
Also, before the cell is redrawn I have the white space, my placeholder, then a jump as the correct image / size is applied.
Is it possible to simply prevent the cell from being visible at all until the image has been applied?
No! You don't have to reload the tableView in any way after the image in a certain cell gets done downloading. Other than that, you're doing everything correctly. I use Kingfisher too.
But in the future, you could just make a function in your cell class, say setupCell(_ imageURL: URL?). Anyways, going on, in your cellForRow method, just do it like this:
cell.gifImage.kf.setImage(with: imageView, placeholder: placeHolderImage, completionHandler: nil)
Now, in case that there's no image downloaded or you have no image URL string to be made to URL object, you could catch it in Kingfisher's error block, then you will need to pass your error image (a UIImage) object to your imageView so that there would be no duplicate image.
For example, I have the following in my cell's setupCell() function:
if let url = vendor.image?.URLEscaped {
let resource = ImageResource(downloadURL: url, cacheKey: url.absoluteString)
self.imageView_Vendor.kf.setImage(with: resource, options: [.transition(.fade(0.2)), .cacheOriginalImage])
return
}
self.imageView_Vendor.image = nil
I hope this helps!
EDIT:
For the handling of image height, this could help: https://stackoverflow.com/a/52787062/3231194

iOS load image into table view cell

update
now images displays as they are loaded, but there still a problem I can't solve.
The images overlaps the content of the cell even the three views are in a vertical stack (first is the image, the next two are label views).
I wonder how can a view overlap other views in a stackview
now my talbe view delegate method looks like this:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell: BaseTableViewCell!
if let contents = contents {
let content = contents[indexPath.row]
if let imageUrl = content.imageUrl, let url = URL(string: imageUrl) {
cell = tableView.dequeueReusableCell(withIdentifier: "ImageContentRow", for: indexPath) as! ContentWithImageTableViewCell
Alamofire.request(url).responseImage(completionHandler: { (response) in
if let image = response.result.value {
cell.imageView?.image = image
cell.setNeedsLayout()
}
})
}else{
cell = tableView.dequeueReusableCell(withIdentifier: "ContentRow", for: indexPath) as! ContentTableViewCell
}
cell.contentTitleVeiw.text = content.title
cell.contentLeadView.text = content.lead
}
return cell!
}
I'm very new to iOS. I've already tutorials and watched video courses and now I would like to implement my very first app.
I try to read a feed and display the content's title, lead and image (if it has any).
I defined a cell, with an UIMageVeiw, and two UILables, at last I embedded them into a vertical StackView (I set distribution to Fill).
Everything works well, but images don't display automatically, just if I click on the cell, or sroll the TableVeiw.
And even if I set the image view to Aspect Fit (and embedded it into the top tof the vertical stack view), when the image displays it keeps it's original size and overlaps the cell content.
I've trying to find out what I do wrong for two days, but I can't solve it.
I try display data like this:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
var cell: BaseTableViewCell!
if let contents = contents {
let content = contents[indexPath.row]
if let imageUrl = content.imageUrl, let url = URL(string: imageUrl) {
cell = tableView.dequeueReusableCell(withIdentifier: "ImageContentRow", for: indexPath) as! ContentWithImageTableViewCell
cell.imageView?.af_setImage(withURL: url)
}else{
cell = tableView.dequeueReusableCell(withIdentifier: "ContentRow", for: indexPath) as! ContentTableViewCell
}
cell.contentTitleVeiw.text = content.title
cell.contentLeadView.text = content.lead
}
return cell!
}
The image view cell's view hierarchy looks like this:
My list looks like this after I start my app and data displayed:
After I click on a cell that should display an image or scroll it out and back the result is this:
At least this is my list view controller with the two prototype cells
(one for contents with image, one for without image)
You need to notify the tableView that the image was loaded and that it has to redraw the cell.
Try changing
cell.imageView?.af_setImage(withURL: url)
to
cell.imageView?.af_setImage(withURL: url, completion: { (_) in
self.tableView.beginUpdates()
self.tableView.setNeedsDisplay()
self.tableView.endUpdates()
})
Take a look at my answer here - in the end you might need to explicitly set height on the imageView.

Issue with displaying tableview cell image with AlamofireImage

I am downloading and showing thumbnails from server with AlamofireImage , the downloading works fine but when I scroll tableivew cell image changes all the time , I have searched and did not find any solution for example prepareForReuse in custom cell calls. Here is my code :
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! TrendingCell
let trendingIndex = trendingArray[indexPath.row] as! [String:Any]
if let media = trendingIndex["thumbnails"] as? [String:Any] {
if let thumbnail = media["medium"] as? [String:Any] {
thumbnailURL = String(describing:thumbnail["url"]!)
}
}
Alamofire.request(thumbnailURL).responseImage { response in
if let image = response.result.value {
cell.thumbnail.image = image
}
}
return cell
}
How to avoid image changing when user scrolls tableview ?
As Salman Ghumsani mentioned here :
we can use AlamofireImage extension to set a default thumbnail like this :
//Download and set thumbnail
if let imageURL = URL(string: thumbnailURL), let placeholder = UIImage(named: "Default") {
cell.thumbnail.af_setImage(withURL: imageURL, placeholderImage: placeholder) //set image automatically when download compelete.
}
Now when you scroll table view images did not change.

UITableViewCell image loading wrong images

For now i download the image, story it in a mutable dictionary and then verify if the image was already downloaded and if not, download it and store it. As a key i use the indexPath.
This code kinda works, but from the tests i did if i scroll too fast the cell image will load the wrong one and after a split of a second it will load the right one (replacing the wrong image).
Im always clearing my thumbnail (imageView) after i call the method so i don't know why im getting this bug.
I though that maybe the if(self.imageCache.object(forKey: cacheKey) != nil) statement was true and thats why i would get multiple images, but the breakpoint didn't stop at once when i was scrolling down.
Any ideas?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! MovieCellController
cell.thumbnail.image = UIImage()
let cacheKey = indexPath.row
if(self.imageCache.object(forKey: cacheKey) != nil)
{
cell.thumbnail.image = self.imageCache.object(forKey: cacheKey) as? UIImage
}
else
{
DispatchQueue.global(qos: DispatchQoS.QoSClass.default).async {
if let url = NSURL(string: self.moviesCollection[indexPath.row].imageLink) {
if let data = NSData(contentsOf: url as URL) {
let image: UIImage = UIImage(data: data as Data)!
self.imageCache.setObject(image, forKey: cacheKey as NSCopying)
DispatchQueue.main.async(execute: {
cell.thumbnail.image = image
})
}
}
}
}
cell.name.text = moviesCollection[indexPath.row].name
return cell
}
It is happening because the cells are reused due to which when scrolling fast the image of another cell seems to be assigned, but if fact it is the previous cell's image which is reused.
In cell's prepareForReuse method set your imageView's image to nil. Like, imageView.image = nil
Because the cell is reused.
The reused-cell keeps its old data.
The new image downloading will cost few seconds so that the reused -cell cannot change the image immediately.
You can use a placeholder-image when downloading the new image.
Or you can use the 3rd part library - SDWebImage.

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

Resources