Loading image from URL is working inconsistently - ios

I am storing URLs as Strings in an Organization. These URLs are used to fetch each Organization's logo and display it in a tableview. Here is the code I am using to do this:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "orgcell", for: indexPath) as! OrgCell
cell.orgAcro.text = filteredOrgs[indexPath.row].acronym
cell.topTag.text = filteredOrgs[indexPath.row].tags[0].title
let data = try? Data(contentsOf: filteredOrgs[indexPath.row].url)
if (data == nil) {
cell.orgImg.image = UIImage()
} else {
cell.orgImg.image = UIImage(data: data!)
}
return cell
}
The only URL this is working for is this one https://pbs.twimg.com/profile_images/718076464856338432/zlcMj0Oo.jpg
It is not working for this one http://www.therightfew.com/wp-content/uploads/2016/09/Planned-Parenthood-Logo-Square.jpg
It has nothing to do with the order by which I load them, I have tried switching it up. Any ideas as to what's going on?

Related

Update ImageView in TableViewCell from function called from CellForRowAt

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"))

Swift tableView with images from API is lagging when scrolling

I have a tableView which I populate with images from an API,
however, it is lagging quite a bit when scrolling. I've tried to use async & run on different threads but cannot get it right.. so how do I fix this?
This is the cellForRowAt-func that I use to set image for every single cell.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "VideoCell", for: indexPath) as! TableViewCell
let video = highlightsArray[indexPath.row]
cell.video = video
do {
let url = URL(string: self.highlightsArray[indexPath.row].thumbnail)!
let data = try Data(contentsOf: url)
cell.thumbnailImage.image = UIImage(data: data)
}
catch{
print(error)
cell.thumbnailImage.image = UIImage(named: "pllogo.jpg")
}
return cell
}
This line
let data = try Data(contentsOf: url)
blocks the main thread and redownloads the same image multiple times when scrolling , consider using SDWebImage

Image repeates in every cell of table view in swift3

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.

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

Images not show in sequence in UITableViewCell

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

Resources