Image not loading in collection view cell - ios

I've programmatically setup a collection view that should display an image from an API onto a cell. However, once the cells are displayed and data is called the cells remain empty.
Cells returning an empty image view
Current setup
override func viewDidLoad() {
super.viewDidLoad()
collectionView.register(ImageCell.self, forCellWithReuseIdentifier: imageCellId)
loadImageData(numberOfItems: numberOfItems)
}
func loadImageData(numberOfItems: Int) {
client.getImageData(items: numberOfItems, completion: { (error,data) in
if error != nil {
print("Error parsing image data")
} else {
self.per_page = data.perPage
self.total_results = data.totalResults
self.images = data.photos
for image in self.images {
self.userNameArray.append(image.photographer)
self.urlArray.append(image.url)
}
self.imageLinkArray = self.urlArray
DispatchQueue.main.async {
self.collectionView.reloadData()
}
}
})
}
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: imageCellId, for: indexPath) as! ImageCell
let imageString = String(imageLinkArray[indexPath.row])
let url = URL(string: imageString)
let data = try? Data(contentsOf: url!)
if let imageData = data {
let imageFromDatabase = UIImage(data: imageData)
cell.imageView.image = imageFromDatabase
}
return cell
}
Tried:
Made sure the URLs are coming back using a print statement for the url constant in cellForItemAt.
I've also tested out the cell layout by using a placeholder image.
Called collectionView.reloadData() in viewDidAppear().
Collection View Cells not appearing
Images not displayed in collection view cell

You should use Kingfisher as an image caching library
https://github.com/onevcat/Kingfisher
First, add the pod to your project:
pod 'Kingfisher'
Replace this:
let url = URL(string: imageString)
let data = try? Data(contentsOf: url!)
if let imageData = data {
let imageFromDatabase = UIImage(data: imageData)
cell.imageView.image = imageFromDatabase
}
with this
let url = URL(string: imageString)
imageView.kf.setImage(with: url)

Related

error while loading images asynchronously from firebase

I have been trying to load images asynchronously for quiet a while now.
When i make a new class for this and built and run the app an error appears in the runtime log
// log
Failed to set (borderWidth) user defined inspected property on ...
could not set nil as the value for the key borderWidth.
could not set nil as the value for the key cornerRadius.
It is fine with extension instead of class, the images load async but, this way I will not be able to make sure the correct image is loaded in the image view.
// cell for item at
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "subscribeCell", for: indexPath) as! subscribeViewCell
cell.userID = self.users[indexPath.row].userId
cell.profileName.text = self.users[indexPath.row].fullName
cell.profileImage.setupWithImageUrl(imageUrl: self.users[indexPath.row].ImagePath)
checkFollowers(indexPath : indexPath)
return cell
}
the class i have been working with in the same view controller :
Class to download image:
private var imageCache = [String:UIImage]()
class CustomImageView : UIImageView {
var lastImageUrl : String?
func setupWithImageUrl (imageUrl: String) {
self.contentMode = .scaleAspectFill
self.clipsToBounds = true
self.layer.cornerRadius = 14
self.layer.borderWidth = 0
lastImageUrl = imageUrl
if let cachedImage = imageCache[imageUrl] {
self.image = cachedImage
return
}
guard let url = URL(string: imageUrl) else {
return
}
URLSession.shared.dataTask(with: url) { (data, response, error) in
if let error = error {
print(error)
return
}
guard let data = data else {
return
}
guard let image = UIImage(data : data) else {
return
}
imageCache[url.absoluteString] = image
}
if (url.absoluteString != self.lastImageUrl) {
return
}
DispatchQueue.main.async {
self.image = self.image
}
}
}
Why "this way" cannot make sure correct image is loaded in the image view. I read your code I think your code is fine for collectionViewController cellForItemAt

Set a default image in TableViewCell

I have tried several different approaches and nothing has yet to work. I am pulling in album artwork for a recently played tableview for my radio station app. I get blank images when there is no album artwork to pull into the cell. I just want to have my station logo "WhiteLogo.png" as a placeholder whenever there is no album artwork pulled into the tableview cell. Any help in the right direction is much appreciated. Thanks
import UIKit
//----------
//MARK: JSON
//----------
//The Initial Response From The JSON
struct Response: Codable {
var playHistory: Album
}
//The Album Received Which Is An Array Of Song Data
struct Album: Codable {
var song: [SongData]
}
//The SongData From The PlayHistory Album
struct SongData: Codable{
var album: String
var artist: String
var cover: String
var duration: String
var programStartTS: String
var title: String
}
class TableViewController: UITableViewController {
//1. Create An Array To Store The SongData
var songs = [SongData]()
var currentStation: RadioStation!
var downloadTask: URLSessionDownloadTask?
override func viewDidLoad() { super.viewDidLoad()
self.tableView.delegate = self
self.tableView.dataSource = self
//2. Load The JSON From The Main Bundle
guard let urlText = URL (string: "http://streamdb3web.securenetsystems.net/player_status_update/JACKSON1_history.txt")
else { return }
do{
//a. Get The Data From The From The File
let data = try Data(contentsOf: urlText)
//b. Decode The Data To Our Structs
let albumData = try JSONDecoder().decode(Response.self, from: data)
//c. Append The Songs Array With The PlayHistory
albumData.playHistory.song.forEach { songs.append($0) }
//d. Test Some Data
print("""
**The First Album Details**
Album = \(songs[0].album)
Artist = \(songs[0].artist)
Cover = \(songs[0].cover)
Duration = \(songs[0].duration)
Start = \(songs[0].programStartTS)
Title = \(songs[0].title)
""")
//3. Load The Data
DispatchQueue.main.async {
self.tableView.reloadData()
}
}catch{
print(error)
}
}
//-----------------
//MARK: UITableView
//-----------------
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return songs.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//1. Create A Cell
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
//2. Set It's Text
cell.songTitle.text = songs[indexPath.row].title
cell.artistLabel.text = songs[indexPath.row].artist
//3. Get The Image
if let imageURL = URL(string: songs[indexPath.row].cover){
let request = URLSession.shared.dataTask(with: imageURL) { (imageData, response, error) in
if let error = error{
print(error)
}else{
guard let image = imageData else { return }
DispatchQueue.main.async {
cell.songCover.image = UIImage(data: image)
cell.setNeedsLayout()
cell.layoutIfNeeded()
}
}
}
request.resume()
}
return cell
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("""
**Album \(indexPath.row) Selected**
Album = \(songs[indexPath.row].album)
Artist = \(songs[indexPath.row].artist)
Cover = \(songs[indexPath.row].cover)
Duration = \(songs[indexPath.row].duration)
Start = \(songs[indexPath.row].programStartTS)
Title = \(songs[indexPath.row].title)
""")
}
}
Just the right case handling is required.
I would set the placeholder image first and then proceed to download an image from a URL.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//...
/*
Start with placeholder image so it shows until the image download completes.
And if the next `if let imageURL` condition fails, the placeholder image will stay
*/
cell.songCover.image = UIImage(named: "WhiteLogo")
//Continue with your logic, no change really but could be shortened to:
if let imageURL = URL(string: songs[indexPath.row].cover) {
let request = URLSession.shared.dataTask(with: imageURL) { (imageData, response, error) in
guard let imageData = imageData else { return }
DispatchQueue.main.async {
cell.songCover.image = UIImage(data: imageData)
}
}
request.resume()
}
//...
}
However, since the image download logic is async, it will misbehave if the cell is reused before the download completes.
i.e. Image download for the first song starts but you scroll fast enough to reuse the first cell for, lets say, the third song.
Now, when the download completes, the first image could show on the third cell.
If you face this issue then let me know and I shall update my answer.
Set "WhiteLogo.png" on above your code which download image for album or set logo image if album image data is nil like guard let image = imageData else { var image : UIImage = UIImage(named:"WhiteLogo.png")!
cell.songCover.image = UIImageView(image: image) }
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//1. Create A Cell
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! TableViewCell
//2. Set It's Text
cell.songTitle.text = songs[indexPath.row].title
cell.artistLabel.text = songs[indexPath.row].artist
//set image
var image : UIImage = UIImage(named:"WhiteLogo.png")!
cell.songCover.image = UIImageView(image: image)
//3. Get The Image
if let imageURL = URL(string: songs[indexPath.row].cover){
let request = URLSession.shared.dataTask(with: imageURL) { (imageData, response, error) in
if let error = error{
print(error)
}else{
guard let image = imageData else { return }
DispatchQueue.main.async {
cell.songCover.image = UIImage(data: image)
cell.setNeedsLayout()
cell.layoutIfNeeded()
}
}
}
request.resume()
}
return cell
}
guard let image = imageData else { cell.songCover.image = UIImage(named : "your_image_name"); return }
Please use the Kingfisher library it will download image from url and set placeholder image.Library URL:- https://github.com/onevcat/Kingfisher

Why can not I set image?

Why can not I set image?
class ViewController:UIViewController,UITableViewDelegate,UITalbeViewDataSource{
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell",for: indexPath) as! CustomTableViewCell
cell.setCell()
return cell
}
}
class CustomTableViewCell:UITableViewCell{
#IBOutlet weak var artworkImage:UIImageView!
func setCell(){
let urlStr = "http://is3.mzstatic.com/image/thumb/Music20/v4/d4/6c/af/d46caf98-ff6c-1707-135d-58d6ed9ea6a2/source/500x500bb.jpg"
let url = URL(strings: urlStr)
let data = try? Data(contentsOf: url!)
if let imageData = data {
self.artworkImage.image = UIImage(data: imageData)
}
}
}
I want to display the image of the URL destination in TableView but the image is not displayed.
this image is log.
setCell() should be like this
func setCell(){
let urlStr = "http://is3.mzstatic.com/image/thumb/Music20/v4/d4/6c/af/d46caf98-ff6c-1707-135d-58d6ed9ea6a2/source/500x500bb.jpg"
let url = URL(string: urlStr)
artworkImage.image = UIImage(named: "ic_placeholder")
DispatchQueue.global(qos: .background).async {
let data = try? Data(contentsOf: url!)
DispatchQueue.main.async {
if let imageData = data {
self.artworkImage.image = UIImage(data: imageData)
}
}
}
}
Image should be downloaded on background thread otherwise UI will be blocked. After downloading images you need to make changes in Main Thread.
Also better have a placeholder image.
You are trying to set image in background thread, because of that you receive this logs. You need to use main thread while displaying image on imageView like:
if let imageData = data {
DispatchQueue.main.async(execute: {
self.artworkImage.image = UIImage(data: imageData)
})
}
Try this Lib for images particularly for ImageView on cell :-
https://github.com/Haneke/HanekeSwift
let URLString = self.items[indexPath.row]
let URL = NSURL(string:URLString)!
cell.imageView.hnk_setImageFromURL(URL)
cell:
class CustomTableViewCell: UITableViewCell {
#IBOutlet weak var artworkImage:UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
func setCell(){
let urlStr = "http://is3.mzstatic.com/image/thumb/Music20/v4/d4/6c/af/d46caf98-ff6c-1707-135d-58d6ed9ea6a2/source/500x500bb.jpg"
let url = URL(string: urlStr)
let data = try? Data(contentsOf: url!)
DispatchQueue.main.async {
if let imageData = data {
self.artworkImage.image = UIImage(data: imageData)
} }
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
Also do not forget. Set up App Transport Security Settings.
good luck

Why swift GCD not work in tableview cell imageView

I made a chat app. When I chatting and post image message to A man. I check my image upload successful,and download to local document failed. A man's view just have imageView with clear background.No image to show,and I click back to previous view,then go back chat view. The image show up. what's happen? how to let image download to local immediatly? Thanks!!
This is my download function code:
func ImageFromUrl(imageView:UIImageView,url:String) {
let documentsDirectoryURL = try! FileManager().url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent("\(Image)/")
let fileName = url + ".jpg"
let fileURL = documentsDirectoryURL.appendingPathComponent(fileName)
let urlString = URL(string: url)
if let image = UIImage(contentsOfFile: fileURL.path)
{
imageView.image = image
return
}
DispatchQueue.global().async {
let data = try? Data(contentsOf: urlString!) //make sure your image in this url does exist, otherwise unwrap in a if let check / try-catch
if data != nil
{
if let image = UIImage(data: data!)
{
if !FileManager.default.fileExists(atPath: fileURL.path) {
if let jpegData = UIImageJPEGRepresentation(image, 0.001)
{
do {
try jpegData.write(to: fileURL, options: .atomic)
} catch {
debug(object: error)
}
}
} else {
debug(object:"file already exists")
}
DispatchQueue.main.async {
imageView.image = image//UIImage(data: data!)
}
}
}
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell: CustomTableViewCell = CustomTableViewCell(style: .default, reuseIdentifier: nil)
if cell.isRight == false { //A man's cell View
ImageFromUrl(imageView:cell.imageView,url:imageUrl)
//tableView.reloadRow(at:indexPath, with: .automatic)
//tableView.reloadRows(at: [indexPath], with: .automatic)
//tableView.reloadRow(UInt(contents.count-1), inSection: 0, with: .automatic)
}
}
Since this is being pushed to GCD the table will finish updating while the image is loaded. Therefore you will need reload the data in the table view.
Change:
DispatchQueue.main.async {
imageView.image = image//UIImage(data: data!)
}
to:
DispatchQueue.main.async {
imageView.image = image//UIImage(data: data!)
self.tableView?.reloadData()
}
But this won't be enough, as the image will be recreated each time tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell is called and you will be caught in an infinite loop. I would recommend subclassing UITableViewCell to create your own cell that contains an image property. Then you can tell ImageFromURL(_) to save the image to the cell's image property, and then reload the view.

Table view lag while displaying images from network

I am creating a small app for displaying images in TableView from Flickr. The app works fine but i am facing a issue while scrolling table view, my tableview lags a lot. I think the issue might be with GCD or Threads, i am new to networking and GCD, this is my code getting images from Flickr API.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = flickrTableView.dequeueReusableCellWithIdentifier("cell") as! FlickerTableCell
let flickr = flickrArray[indexPath.row]
dispatch_async(dispatch_get_main_queue(), {
cell.imageViewFlickr.image = flickr.getFlickrImage()
})
cell.labelFlickrTitle.text = flickr.title
return cell
}
// function to get images from flickr
func getFlickrImage()->UIImage{
let imageURL = NSURL(string: "https://farm\(farm).staticflickr.com/\(server)/\(photoId)_\(secret)_m.jpg")!
var flickrImage = UIImage()
if let imageData = NSData(contentsOfURL: imageURL) {
flickrImage = UIImage(data: imageData)!
} else {
print("Image does not exist at \(imageURL)")
}
return flickrImage
}
Your method to fetch image from network that is getFlickrImage() is sync method so table cell waits for its response and as the network call is in main thread it freezes or lags the UI. you can wrap your network call in async method and use completion handler to update the UI in main thread such as:
func getFlickrImage(completion:UIImage ->()){
let imageURL = NSURL(string: "https://farm\(farm).staticflickr.com/\(server)/\(photoId)_\(secret)_m.jpg")!
var flickrImage = UIImage()
let download = dispatch_queue_create("download", nil)
dispatch_async(download) {
if let imageData = NSData(contentsOfURL: imageURL) {
dispatch_async(dispatch_get_main_queue(), {
flickrImage = UIImage(data: imageData)!
completion(flickrImage)
})
} else {
print("Image does not exist at \(imageURL)")
completion(flickrImage)
}
}
}
Your cellForRowAtIndexPath will be like:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = flickrTableView.dequeueReusableCellWithIdentifier("cell") as! FlickerTableCell
let flickr = flickrArray[indexPath.row]
flickr.getFlickrImage { (photo) in
dispatch_async(dispatch_get_main_queue(), {
cell.imageViewFlickr.image = photo
})
}
cell.labelFlickrTitle.text = flickr.title
return cell
}

Resources