Why swift GCD not work in tableview cell imageView - ios

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.

Related

Image not loading in collection view cell

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)

Swift KingFisher in UITableViewCell image From Url not dynamical

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = chatableView.dequeueReusableCell(withIdentifier: "SendMsgCell", for: indexPath) as! SendMsgTableViewCell
let msg = chatList[indexPath.row]
if msg.emoURL != nil{
cell.sticonFrame.kf.setImage(with: getEmojiFromURL(msg.emoURL!),completionHandler:{
(image, error, cacheType, imageUrl) in
if image != nil{
cell.sticonFrame.alpha = 1
}else{
cell.sticonFrame.alpha = 0
cell.sticonFrame.image = nil
}
})
cell.layoutIfNeeded()
}else{
cell.sticonFrame.alpha = 0
cell.sticonFrame.image = nil
}
return cell
}
KingFisher can't load First view
First scroll down can't see image
if want see image You have to come down after scroll up
layoutIfNeeded is not solved i haven't idea
Will see it dynamic after Once call image,
I don't think there is some error please check your URL is not empty and maybe image size is big.
//go for this appoarch
class INSUtilities {
class func setImage(onImageView image:UIImageView,withImageUrl imageUrl:String?,placeHolderImage: UIImage) {
if let imgurl = imageUrl{
image.kf.indicatorType = IndicatorType.activity
image.kf.setImage(with: URL(string: imgurl), placeholder: placeHolderImage, options: nil, progressBlock: nil, completionHandler: { (image, error, cacheType, url) in
})
}
else{
image.image = placeHolderImage
}
}
}
//incellclass call like this
//logoImg is the name of imageView
INSUtilities.setImage(onImageView: cell.sticonFrame, withImageUrl: logo!, placeHolderImage: #imageLiteral(resourceName: "pic_PlaceHolder"))

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

tableView cell is displaying the wrong image sometimes?

I have a tableView that displays an image in the cell. Most of the time the correct image will be displayed, however occasionally it will display the wrong image (usually if scrolling down the tableView very quickly). I download the images asynchronously and store them in a cache. Can't find what else could be causing the issue?? Below is the code:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
// try to reuse cell
let cell:CustomCell = tableView.dequeueReusableCellWithIdentifier("DealCell") as CustomCell
// get the venue image
let currentVenueImage = deals[indexPath.row].venueImageID
let unwrappedVenueImage = currentVenueImage
var venueImage = self.venueImageCache[unwrappedVenueImage]
let venueImageUrl = NSURL(string: "http://notrealsite.com/restaurants/\(unwrappedVenueImage)/photo")
// reset reused cell image to placeholder
cell.venueImage.image = UIImage(named: "placeholder venue")
// async image
if venueImage == nil {
let request: NSURLRequest = NSURLRequest(URL: venueImageUrl!)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler: {(response: NSURLResponse!,data: NSData!,error: NSError!) -> Void in
if error == nil {
venueImage = UIImage(data: data)
self.venueImageCache[unwrappedVenueImage] = venueImage
dispatch_async(dispatch_get_main_queue(), {
// fade image in
cell.venueImage.alpha = 0
cell.venueImage.image = venueImage
cell.venueImage.fadeIn()
})
}
else {
}
})
}
else{
cell.venueImage.image = venueImage
}
return cell
}
Swift 3
I write my own light implementation for image loader with using NSCache.
No cell image flickering!
ImageCacheLoader.swift
typealias ImageCacheLoaderCompletionHandler = ((UIImage) -> ())
class ImageCacheLoader {
var task: URLSessionDownloadTask!
var session: URLSession!
var cache: NSCache<NSString, UIImage>!
init() {
session = URLSession.shared
task = URLSessionDownloadTask()
self.cache = NSCache()
}
func obtainImageWithPath(imagePath: String, completionHandler: #escaping ImageCacheLoaderCompletionHandler) {
if let image = self.cache.object(forKey: imagePath as NSString) {
DispatchQueue.main.async {
completionHandler(image)
}
} else {
/* You need placeholder image in your assets,
if you want to display a placeholder to user */
let placeholder = #imageLiteral(resourceName: "placeholder")
DispatchQueue.main.async {
completionHandler(placeholder)
}
let url: URL! = URL(string: imagePath)
task = session.downloadTask(with: url, completionHandler: { (location, response, error) in
if let data = try? Data(contentsOf: url) {
let img: UIImage! = UIImage(data: data)
self.cache.setObject(img, forKey: imagePath as NSString)
DispatchQueue.main.async {
completionHandler(img)
}
}
})
task.resume()
}
}
}
Usage example
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Identifier")
cell.title = "Cool title"
imageLoader.obtainImageWithPath(imagePath: viewModel.image) { (image) in
// Before assigning the image, check whether the current cell is visible for ensuring that it's right cell
if let updateCell = tableView.cellForRow(at: indexPath) {
updateCell.imageView.image = image
}
}
return cell
}
I think the issue is with sendAsynchronousRequest. If you are scrolling faster than this is taking, when you reuse a cell, you can end up with the old completionHandler replacing the "wrong" cell (since it's now showing a different entry). You need to check in the completion handler that it's still the image you want to show.
So after some of the previous answers pointing me in the right direction, this is the code I added, which seems to have done the trick. The images all load and are displayed as they should now.
dispatch_async(dispatch_get_main_queue(), {
// check if the cell is still on screen, and only if it is, update the image.
let updateCell = tableView .cellForRowAtIndexPath(indexPath)
if updateCell != nil {
// fade image in
cell.venueImage.alpha = 0
cell.venueImage.image = venueImage
cell.venueImage.fadeIn()
}
})
This is the problem with dequeued re-usable cell. Inside the image download completion method, you should check whether this downloaded image is for correct index-path. You need to store a mapping data-structure that stores the index-path and a corresponding url. Once the download completes, you need to check whether this url belongs to current indexpath, otherwise load the cell for that downloaded-indexpath and set the image.
The following code changes worked me.
You can download the image in advance and save it in the application directory which is not accessible by the user. You get these images from the application directory in your tableview.
// Getting images in advance
var paths = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as! String
var dirPath = paths.stringByAppendingPathComponent("XYZ/")
var imagePath = paths.stringByAppendingPathComponent("XYZ/\(ImageName)" )
println(imagePath)
var checkImage = NSFileManager.defaultManager()
if checkImage.fileExistsAtPath(imagePath) {
println("Image already exists in application Local")
} else {
println("Getting Image from Remote")
checkImage.createDirectoryAtPath(dirPath, withIntermediateDirectories: true, attributes: nil, error: nil)
let request: NSURLRequest = NSURLRequest(URL: NSURL(string: urldata as! String)!)
let mainQueue = NSOperationQueue.mainQueue()
NSURLConnection.sendAsynchronousRequest(request, queue: mainQueue, completionHandler: { (response, data, error) -> Void in
if let httpResponse = response as? NSHTTPURLResponse {
// println("Status Code for successful------------------------------------>\(httpResponse.statusCode)")
if (httpResponse.statusCode == 502) {
//Image not found in the URL, I am adding default image
self.logoimg.image = UIImage(named: "default.png")
println("Image not found in the URL")
} else if (httpResponse.statusCode == 404) {
//Image not found in the URL, I am adding default image
self.logoimg.image = UIImage(named: "default.png")
println("Image not found in the URL")
} else if (httpResponse.statusCode != 404) {
// Convert the downloaded data in to a UIImage object
let image = UIImage(data: data)
// Store the image in to our cache
UIImagePNGRepresentation(UIImage(data: data)).writeToFile(imagePath, atomically: true)
dispatch_async(dispatch_get_main_queue(), {
self.logoimg.contentMode = UIViewContentMode.ScaleAspectFit
self.logoimg.image = UIImage(data: data)
println("Image added successfully")
})
}
}
})
}
// Code in cellForRowAtIndexPath
var checkImage = NSFileManager.defaultManager()
if checkImage.fileExistsAtPath(imagePath) {
println("Getting Image from application directory")
let getImage = UIImage(contentsOfFile: imagePath)
imageView.backgroundColor = UIColor.whiteColor()
imageView.image = nil
imageView.image = getImage
imageView.frame = CGRectMake(xOffset, CGFloat(4), CGFloat(30), CGFloat(30))
cell.contentView.addSubview(imageView)
} else {
println("Default image")
imageView.backgroundColor = UIColor.whiteColor()
imageView.image = UIImage(named: "default.png")!
imageView.frame = CGRectMake(xOffset, CGFloat(4), CGFloat(30), CGFloat(30))
cell.contentView.addSubview(imageView)
}
Swift 5
So a simple solution to your problem would be by creating a custom class which subclasses UIImageView.
Add a property to store the url string.
Initially set the image to your placeholder to stop flickering.
While parsing and setting the image from response data compare class property url string with the url string passed through to the function and make sure they are equal.
Cache your image with the key as the url string and retrieve accordingly.
Note: Do not extend UIImageview as we plan to add property imageUrl.
reference: https://www.youtube.com/watch?v=XFvs6eraBXM
let imageCache = NSCache<NSString, UIImage>()
class CustomIV: UIImageView {
var imageUrl: String?
func loadImage(urlStr: String) {
imageUrl = urlStr
image = UIImage(named: "placeholder venue")
if let img = imageCache.object(forKey: NSString(string: imageUrl!)) {
image = img
return
}
guard let url = URL(string: urlStr) else {return}
imageUrl = urlStr
URLSession.shared.dataTask(with: url) { data, response, error in
if let err = error {
print(err)
} else {
DispatchQueue.main.async {
let tempImg = UIImage(data: data!)
if self.imageUrl == urlStr {
self.image = tempImg
}
imageCache.setObject(tempImg!, forKey: NSString(string: urlStr))
}
}
}.resume()
}
}
Just update you tableview cell's imageview to a CustomIV object.
And then update the image using:
cell.venueImage.loadImage(urlStr: "http://notrealsite.com/restaurants/\(unwrappedVenueImage)/photo")

Resources