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.
Related
I try to update an ImageView witch is part of my TableViewCell. The each cell has other countries, and for each cell I want to download the county's flag and show it in the cell´s ImageView, so I use CellForRowAt, ready the cell´s country and call a function which downloads the image of the flag. But I don't get, how I can update the ImageView in the Cell...
Here is my code:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! CustomCell
// Configure the cell...
let stock = stocks[indexPath.row]
// Image downloading
loadCountryImage(country: stock.country)
return cell
}
func loadCountryImage(country: String) {
let url = "https://www.countryflags.io/\(country)/shiny/24.png"
guard let imageURL = URL(string: url) else {
print("no URL found")
return
}
let session = URLSession.shared
session.dataTask(with: imageURL) { imageData, _, _ in
let image = UIImage(data: imageData!)
}.resume()
So now the image is downloaded successfully, but how do I get it in the imageView of the cell?
Kind regards from Germany!
Yannik
The short answer is to pass the cell to loadCountryImage so that you can update the cell image in the closure from your data task. This update needs to be dispatched onto the main queue.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! CustomCell
// Configure the cell...
let stock = stocks[indexPath.row]
cell.imageView.image = somePlaceholderImage
// Image downloading
loadCountryImage(country: stock.country, in: cell)
return cell
}
func loadCountryImage(country: String, in cell:CustomCell) {
let url = "https://www.countryflags.io/\(country)/shiny/24.png"
guard let imageURL = URL(string: url) else {
print("no URL found")
return
}
let session = URLSession.shared
session.dataTask(with: imageURL) { imageData, _, _ in
guard let data = imageData, let image = UIImage(data: data) else {
return
}
DispatchQueue.main.async {
cell.imageView.image = image
}
}.resume()
}
The long answer is that you should consider caching and since cells are reused, what happens when the table view scrolls? You may fetch an image that is out of date. One approach is to store the country in the cell and check in the closure to see it is still what you expect before you set the image.
You can handle some of this yourself using UITableviewDatasourcePrefetching and NSCache
var imageCache = NSCache<NSString,UIImage>()
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath) as! CustomCell
// Configure the cell...
let stock = stocks[indexPath.row]
cell.imageView.image = somePlaceholderImage
cell.country = stock.country
// Image downloading
loadCountryImage(country: stock.country, in: cell)
return cell
}
func tableView(_ tableView: UITableView, prefetchRowsAt indexPaths: [IndexPath]) {
for indexPath in indexPaths {
let stock = stocks[indexPath.row]
if self.cache.object(forKey: stock.country) == nil {
self.loadCountryImage(country: stock.country, in: nil)
}
}
}
func loadCountryImage(country: String, in cell:CustomCell?) {
if let image = self.imageCache.object(forKey: country) {
cell?.imageView.image = image
return
}
let url = "https://www.countryflags.io/\(country)/shiny/24.png"
guard let imageURL = URL(string: url) else {
print("no URL found")
return
}
let session = URLSession.shared
session.dataTask(with: imageURL) { imageData, _, _ in
guard let data = imageData, let image = UIImage(data: data) else {
return
}
DispatchQueue.main.async {
self.imageCache.setObject(image, forKey: country)
if cell?.country == country {
cell?.imageView.image = image
}
}
}.resume()
}
A better answer is probably to look at frameworks like SDWebImage or Kingfisher that do a lot of this for you.
With Existing solution you can pass cell to to your function to set image image when download. but this approach leads to performance issue while scrolling your TableView, because cells are reusing and your cellForRowAtIndexPath called each time when that specific row will be visible in mobile screen window and your download function also got triggered. As cell are reusing you also encounter problem of display same image in multiple cell. the better solution is to use SDWebImage form cocoaPod. you only have to focus on development . rest will be manage by SDWebImage i.e performance ,cache etc
Add SDWebImage image in pod file
pod 'SDWebImage'
Install pod by running
pod install
import SDWebImage image in your viewController
import SDWebImage
set image in cellforRowAt by
cell.yourImageViewInCell.sd_setImage(with: URL(string: "http://youserver.com/path/to/image.jpg"), placeholderImage: UIImage(named: "placeholder.png"))
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.
My JsonData -
let imagestring : String? = (myData as AnyObject).value(forKey: "Post_mid_image") as? String
if imagestring != nil {
let imageTrueString = "https://www.zdoof.com/" + imagestring!
self.imageStringArray.append(imageTrueString )
}
if let NameString = (myData as AnyObject).value(forKey: "Name") as? String {
self.nameStringArray.append(NameString)
}
When i am trying to set it to the table view cell
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.postLableArray.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "reUse", for: indexPath)
let myImage = cell.viewWithTag(30) as! UIImageView
myImage.clipsToBounds = true
if indexPath.row < imageStringArray.count {
if let myImageString = imageStringArray[indexPath.row] as? String {
let ImageUrl = URL.init(string: myImageString)
myImage.kf.setImage(with: ImageUrl)
}
}
return cell
}
The image is repeating in every cell . Why it is happening ? Please help
As per the response you have given, you can show the image like below:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let dict = myData as AnyObject
let cell = tableView.dequeueReusableCell(withIdentifier: "reUse", for: indexPath)
let myImage = cell.viewWithTag(30) as! UIImageView
if dict["Post_mid_image"] != nil {
let imageUrl = URL.init(string: strImageUrl)
myImage.kf.setImage(with: imageUrl)
} else {
//Set placeholder image showing no image available
}
return cell
}
Problem is with cell re-usablity of table view here ,you have to handle it , you can have SDWebImage library for loading images in cell or you can have your own image cache which caches images with key/values , key as in image url , so dynamically checking image url for item at indexpath and load cached image with that image url as key.
Hope it helps!!
This is happening because of using tableView.dequeueReusableCell(withIdentifier: "reUse", for: indexPath).
Basically whenever you use dequeueReusableCell(withIdentifier:,For:), it will use the same cell for all of data. It means the total number of cell which are on screen are only going to load, for all other cell, it will use same cell with different value.
now consider a scenario that you are having 500 cells in tableview, but we can manage at most 10-15 cells in display, so for all other cells it will use same cells just modify the value of cell.
so what you can do here is, whenever you use if statement, don't forgot to add else too.
because for one scenario if cell's background is set to red, than we need to add else for another scenario, as cells are just repeated.
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
I'm trying to show images from XML enclosure to tableViewCell image. Images are show but not in sequence, due to dequeueReusableCellWithIdentifier because when i scroll tableViewCell up and down it change images and not show in sequence according to array index. I've tried different ways but did't get success'
Can anyone please tell me how can show images in sequence, or is there any way that first download all images and then show in cell image??
Or any other quick or easy method instead using dispatch_async.
Thanks
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell : ImageCell2 = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! ImageCell2
cell.titleLabel.text = posts.objectAtIndex(indexPath.row).valueForKey("title") as! NSString as String
downloadFileFromURL(NSURL(string: self.posts.objectAtIndex(indexPath.row).valueForKey("enclosure") as! String)!, completionHandler:{(img) in
dispatch_async(dispatch_get_main_queue(), { () -> Void in
cell.sideImageView.image = img
})
})
return cell
}
UPDATE
Now i tried this
let picURL = self.posts.objectAtIndex(indexPath.row).valueForKey("enclosure") as! String
let url = NSURL(string: picURL)
let data = NSData(contentsOfURL: url!)
cell.sideImageView?.image = UIImage(data: data!)
It show images in sequence but make scrolling hard?
Update2
Now i've tried this
var check = true
var imageArrayNsData : [NSData] = []
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell : ImageCell2 = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! ImageCell2
cell.titleLabel.text = posts.objectAtIndex(indexPath.row).valueForKey("title") as! NSString as String
if check == true{
var indeX = 0
for i in posts.valueForKey("enclosure") as! [NSString]{
let picURL = self.posts.objectAtIndex(indeX).valueForKey("enclosure") as! String
let url = NSURL(string: picURL)
let data = NSData(contentsOfURL: url!)
print("download")
imageArrayNsData.append(data!)
indeX++
print(indeX)
}
check = false
}
if check == false{
cell.sideImageView.image = UIImage(data: imageArrayNsData[indexPath.row])
}
return cell
}
This method only download images one time. And after downloading images it appends in array and next time it show images from array without downloading again. But this method is little bit hard for scrolling. Any one have idea why?
The problem is that the cell object may have been already reused by the time you set the image. You need to add a check to make sure the cell still represents the content you want. That could be as simple as:
if tableView.indexPathForCell(cell) == indexPath {
cell.sideImageView.image = img
}
But might need to be more complex if the index path for a specific item might change in that time (for example, if the user can insert/delete rows).
You could also use a library like AlamofireImage which handles this work (in a different way) for you. With AlamofireImage, your code would look like:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell : ImageCell2 = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! ImageCell2
cell.titleLabel.text = posts.objectAtIndex(indexPath.row).valueForKey("title") as! NSString as String
let URL = NSURL(string: self.posts.objectAtIndex(indexPath.row).valueForKey("enclosure") as! String)!
cell.sideImageView.af_setImageWithURL(URL)
return cell
}
To download asynchronously images and set to UIImageView of your UITableViewCell, you can add an extension to your UIImageView.
extension UIImageView {
func downloadImageFrom(link link:String, contentMode: UIViewContentMode) {
//in my methods, I have a cache to avoid re-downloading my images. Images in cache are identified by its URL
if let _imageData = ImageCache.shareCache.getImageData(link) {
self.image = UIImage(data: _imageData)
return
}
//else, download image
NSURLSession.sharedSession().dataTaskWithURL( NSURL(string:link)!, completionHandler: {
(data, response, error) -> Void in
dispatch_async(dispatch_get_main_queue()) {
self.contentMode = contentMode
if let data = data {
ImageCache.shareCache.cacheImageData(data, imageId: link)
self.image = UIImage(data: data)
}
}
}).resume()
}
}
then, from your call-back cellforrow,
let cell : ImageCell2 = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! ImageCell2
cell.titleLabel.text = posts.objectAtIndex(indexPath.row).valueForKey("title") as! NSString as String
cell.imageView.downloadImageFrom(yourImageUrl)
return cell