Custom UItableViewCell not loading Image? Swift 4 - ios

I'm new at Xcode and swift and ran into this bug. I've searched around a bit and could not find anything on this topic. I have an extension for UIImage that allows me to cache images to the phone here :
import UIKit
let imageCache = NSCache<AnyObject, AnyObject>()
extension UIImageView{
func loadImageUsingCacheWithUrlString(urlString : String)
{
self.image = nil;
// check cache for image first
if let cachedImage = imageCache.object(forKey: urlString as AnyObject) as? UIImage {
self.image = cachedImage;
return
}
let url = URL(string: urlString)
URLSession.shared.dataTask(with: url!, completionHandler: { (data, response, error) in
//download hit an error
if error != nil {
print(error!)
return
}
DispatchQueue.main.async {
if let downloadedImage = UIImage(data: data!){
imageCache.setObject(downloadedImage, forKey: urlString as AnyObject)
self.image = downloadedImage
}
}
}).resume()
}
}
It is not loading the image into a table views image view:( Ignore random text )
Table view not loading image
Here is also the UItableView from the main.storyboard:
Updated main.storyboard screen shot
Here is my cellForRowAt: indexPath method where the image is suppose to be loaded:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cellid" , for: indexPath) as! CustomChatTableViewCell;
let gray = UIColor(red:0.94, green:0.94, blue:0.94, alpha:1.0)
let red = UIColor(red:1.00, green:0.22, blue:0.37, alpha:1.0)
let message = messages[indexPath.item]
if message.toId == user?.toId{
cell.messageBackground.backgroundColor = red
cell.messageLabel.textColor = UIColor.white
}
else{
cell.messageBackground.backgroundColor = gray
cell.messageLabel.textColor = UIColor.black
}
cell.messageLabel.text = message.text
if let imageUrl = message.imageUrl{
print(imageUrl)
cell.messageImage.loadImageUsingCacheWithUrlString(urlString: imageUrl)
cell.messageImage.isHidden = false;
cell.messageLabel.isHidden = true
//cell.messageBackground.isHidden = true;
}
else
{
cell.messageImage.isHidden = true;
cell.messageLabel.isHidden = false
cell.messageBackground.isHidden = false;
}
return cell;
}
Expected Result:
Images load into cells
Observed Result
Images dont load into the cells :(
these lines of code :
if let imageUrl = message.imageUrl{
print(imageUrl)
cell.messageImage.loadImageUsingCacheWithUrlString(urlString: imageUrl)
Actually print a valid URL string for an image on my firebase database, which is confusing because It is not loading the image.
Important
I use the loadImageUsingCacheWithUrlString method in other parts of my project and it works fine so I don't think its the method.... whats going on?? thank you so much if you can solve this you are an amazing coder!!
I can put an image in the main.storyboard and it works... so I dont know what could be going wrong... :(
screen shot of updated main.storyboard
Image seems to be fine in Extension :
code with breakpoint and console showing
Not sure If the image Is being covered Up in Capture View Hierarchy :
View Hierarchy

In your storyboard, the imageView messageImage is a subview of the view messageBackground. In your if-statement in cellForRow method, you are setting the messageBackground to be hidden
if let imageUrl = message.imageUrl{
print(imageUrl)
cell.messageImage.loadImageUsingCacheWithUrlString(urlString: imageUrl)
cell.messageImage.isHidden = false;
cell.messageLabel.isHidden = true
cell.messageBackground.isHidden = true; //THIS IS THE CULPRIT
}
Since messageBackground is hidden, it's subviews are hidden as well. Might need to rethink your business logic here.

Related

Swift Image Cache not Reloading

I'm having problems cacheing for images from JSON correctly with this UIImageView extension. The images load correctly when I first open the app and scroll down the page. However when I scroll back up, they don't reload and are completely gone. Can anyone see anything wrong with the code?
let imageCache = NSCache<AnyObject, AnyObject>()
extension UIImageView {
func loadImageUsingUrlString(urlString: String) {
let url = NSURL(string: urlString)
if let imageFromCache = imageCache.object(forKey: urlString as AnyObject) as? UIImage {
self.image = imageFromCache
return
}
URLSession.shared.dataTask(with: url! as URL) { (data, response, error) in
if error != nil {
print(error ?? "URLSession error")
return
}
DispatchQueue.main.async {
let imageToCache = UIImage(data: data!)
imageCache.setObject(imageToCache!, forKey: urlString as AnyObject)
self.image = imageToCache
}
}.resume()
}
}
Here is the snippet from the cell.swift file
let imageCache = NSCache<AnyObject, AnyObject>()
func setupThumbnailImage() {
if let thumbnailImageUrl = television?.poster_url {
let urlPrefix = "https://www.what-song.com"
let urlSuffix = thumbnailImageUrl
let urlCombined = urlPrefix + urlSuffix
thumbnailImageView.loadImageUsingUrlString(urlString: urlCombined)
}
}
I suggest using kingFisher, it is very easy to use and it manages all starting from cache threads etc.
let imageResource = ImageResource(downloadURL:URL(string: imagePath )!,cacheKey: imagePath )
viewImage.kf.indicatorType = .activity
viewImage.kf.setImage(with: resource)
where imagePath is the url of your image and viewImage is your imageView
Most probably you would be calling it in wrong way.
Remember that in tableView you reuse the cells.
By the time response comes back for the URLSessionTask you would have already scrolled up/down. In that case self.image would be assigned to the currently visible cell.
Please add your cellForRow code in question.

Swift 3 : URL Image makes UITableView scroll slow issue

I have an extension to print image URL on UIImageView. But I think the problem is my tableView is so slow because of this extension. I think I need to open thread for it. How can I create a thread in this extension or do you know another solution to solve this problem?
My code :
extension UIImageView{
func setImageFromURl(stringImageUrl url: String){
if let url = NSURL(string: url) {
if let data = NSData(contentsOf: url as URL) {
self.image = UIImage(data: data as Data)
}
}
}
}
You can use the frameworks as suggested here, but you could also consider "rolling your own" extension as described in this article
"All" you need to do is:
Use URLSession to download your image, this is done on a background thread so no stutter and slow scrolling.
Once done, update your image view on the main thread.
Take one
A first attempt could look something like this:
func loadImage(fromURL urlString: String, toImageView imageView: UIImageView) {
guard let url = URL(string: urlString) else {
return
}
//Fetch image
URLSession.shared.dataTask(with: url) { (data, response, error) in
//Did we get some data back?
if let data = data {
//Yes we did, update the imageview then
let image = UIImage(data: data)
DispatchQueue.main.async {
imageView.image = image
}
}
}.resume() //remember this one or nothing will happen :)
}
And you call the method like so:
loadImage(fromURL: "yourUrlToAnImageHere", toImageView: yourImageView)
Improvement
If you're up for it, you could add a UIActivityIndicatorView to show the user that "something is loading", something like this:
func loadImage(fromURL urlString: String, toImageView imageView: UIImageView) {
guard let url = URL(string: urlString) else {
return
}
//Add activity view
let activityView = UIActivityIndicatorView(activityIndicatorStyle: .gray)
imageView.addSubview(activityView)
activityView.frame = imageView.bounds
activityView.translatesAutoresizingMaskIntoConstraints = false
activityView.centerXAnchor.constraint(equalTo: imageView.centerXAnchor).isActive = true
activityView.centerYAnchor.constraint(equalTo: imageView.centerYAnchor).isActive = true
activityView.startAnimating()
//Fetch image
URLSession.shared.dataTask(with: url) { (data, response, error) in
//Done, remove the activityView no matter what
DispatchQueue.main.async {
activityView.stopAnimating()
activityView.removeFromSuperview()
}
//Did we get some data back?
if let data = data {
//Yes we did, update the imageview then
let image = UIImage(data: data)
DispatchQueue.main.async {
imageView.image = image
}
}
}.resume() //remember this one or nothing will happen :)
}
Extension
Another improvement mentioned in the article could be to move this to an extension on UIImageView, like so:
extension UIImageView {
func loadImage(fromURL urlString: String) {
guard let url = URL(string: urlString) else {
return
}
let activityView = UIActivityIndicatorView(activityIndicatorStyle: .gray)
self.addSubview(activityView)
activityView.frame = self.bounds
activityView.translatesAutoresizingMaskIntoConstraints = false
activityView.centerXAnchor.constraint(equalTo: self.centerXAnchor).isActive = true
activityView.centerYAnchor.constraint(equalTo: self.centerYAnchor).isActive = true
activityView.startAnimating()
URLSession.shared.dataTask(with: url) { (data, response, error) in
DispatchQueue.main.async {
activityView.stopAnimating()
activityView.removeFromSuperview()
}
if let data = data {
let image = UIImage(data: data)
DispatchQueue.main.async {
self.image = image
}
}
}.resume()
}
}
Basically it is the same code as before, but references to imageView has been changed to self.
And you can use it like this:
yourImageView.loadImage(fromURL: "yourUrlStringHere")
Granted...including SDWebImage or Kingfisher as a dependency is faster and "just works" most of the time, plus it gives you other benefits such as caching of images and so on. But I hope this example shows that writing your own extension for images isn't that bad...plus you know who to blame when it isn't working ;)
Hope that helps you.
I think, that problem here, that you need to cache your images in table view to have smooth scrolling. Every time your program calls cellForRowAt indexPath it downloads images again. It takes time.
For caching images you can use libraries like SDWebImage, Kingfisher etc.
Example of Kingfisher usage:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "identifier", for: indexPath) as! CustomCell
cell.yourImageView.kf.setImage(with: URL)
// next time, when you will use image with this URL, it will be taken from cache.
//... other code
}
Hope it helps
Your tableview slow because you load data in current thread which is main thread. You should load data other thread then set image in main thread (Because all UI jobs must be done in main thread). You do not need to use third party library for this just change your extension with this:
extension UIImageView{
func setImageFromURl(stringImageUrl url: String){
if let url = NSURL(string: url) {
DispatchQueue.global(qos: .default).async{
if let data = NSData(contentsOf: url as URL) {
DispatchQueue.main.async {
self.image = UIImage(data: data as Data)
}
}
}
}
}
}
For caching image in background & scroll faster use SDWebImage library
imageView.sd_setImage(with: URL(string: "http://image.jpg"), placeholderImage: UIImage(named: "placeholder.png"))
https://github.com/rs/SDWebImage

Read image from cache for app ios with swift

I'm currently reading images from my firebase storage - which works fine.
I have set up a caching to read images from the cache when it has been read from the storage:
// Storage.imageCache.object(forKey: post.imageUrl as NSString)
static func getImage(with url: String, completionHandler: #escaping (UIImage) -> ())
{
if let image = imageCache.object(forKey: url as NSString)
{
print("CACHE: Unable to read image from CACHE ")
completionHandler(image)
}
else
{
let ref = FIRStorage.storage().reference(forURL: url)
ref.data(withMaxSize: 2 * 1024 * 1024)
{
(data, error) in
if let error = error
{
print("STORAGE: Unable to read image from storage \(error)")
}
else if let data = data
{
print("STORAGE: Image read from storage")
if let image = UIImage(data: data)
{
// Caches the image
Storage.imageCache.setObject(image, forKey: url as NSString)
completionHandler(image)
}
}
}
}
}
}
But its not working. It seems to not work at all as well, I don't have the message ' print("CACHE: Unable to read image from CACHE ")
' being displayed on my console but the print ' print("STORAGE: Image read from storage")
'
Do you know how this can be achieved by any chance please?
Thanks a lot for your time!
---EDIT --
I call the image in table cell view from firebase storage then as:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = self.feedTableView.dequeueReusableCell(withIdentifier: "MessageCell")! as UITableViewCell
let imageView = cell.viewWithTag(1) as! UIImageView
let titleLabel = cell.viewWithTag(2) as! UILabel
let linkLabel = cell.viewWithTag(3) as! UILabel
titleLabel.text = posts[indexPath.row].title
titleLabel.numberOfLines = 0
linkLabel.text = posts[indexPath.row].link
linkLabel.numberOfLines = 0
Storage.getImage(with: posts[indexPath.row].imageUrl){
postPic in
imageView.image = postPic
}
return cell
}
You can realize caching images with Kingfisher for example. And works better. link
How to use: Add link to your image from storage to database item node. Like this:
Then just use it to present and cache image.
Example:
let imageView = UIImageView(frame: frame) // init with frame for example
imageView.kf.setImage(with: <urlForYourImageFromFireBase>) //Using kf for caching images
Hope it helps

UITableView is not smoothly when using downloading images

I am new in IOS development using Swift. I created 1 UITableView and displaying images after downloading data. But it is not smooth and some time images are displaying in wrong place when i am scrolling.
I am using AlamofireImage library for image downloading and displaying. Is there any fast library?
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:HomePageCell = tableView.dequeueReusableCell(withIdentifier: "HomePage", for: indexPath) as! HomePageCell
cell.configure( homeData[0], row: indexPath, screenSize: screenSize,
hometableview: self.homeTableView);
return cell
}
import UIKit
import Alamofire
import AlamofireImage
class HomePageCell: UITableViewCell {
#IBOutlet weak var bannerImage: UIImageView!
func configure(_ homeData: HomeRequest, row: IndexPath, screenSize: CGRect, hometableview: UITableView) {
let callData = homeData.banner_lead_stories[(row as NSIndexPath).row]
let url = Constants.TEMP_IMAGE_API_URL + callData.lead_story[0].bg_image_mobile;
if( !callData.lead_story[0].bg_image_mobile.isEmpty ) {
if bannerImage?.image == nil {
let range = url.range(of: "?", options: .backwards)?.lowerBound
let u = url.substring(to: range!)
Alamofire.request(u).responseImage { response in
debugPrint(response)
//print(response.request)
// print(response.response)
// debugPrint(response.result)
if let image = response.result.value {
// print("image downloaded: \(image)")
self.bannerImage.image = image;
self.bannerImage.frame = CGRect(x: 0, y: 0, width: Int(screenSize.width), height: Int(screenSize.width/1.4))
}
}
}
} else {
self.bannerImage.image = nil;
}
}
}
It can be not smooth, because you need to cache your images and make a downloading process not in main thread(read about GCD).
For caching you can go two ways (atleast):
1) Make your own array of images where they will be cached
2) Use KingFisher for example click. It will cache your images.
For example:
yourImageView.kf.setImage(with: URL) // next time, when you will use image with this URL, it will be taken from cache.
Hope it helps
You can use SDWebImage for downloading the image array and add a placeholder image for the time being in imageView. this is function
public func sd_setImageWithURL(url: NSURL!, placeholderImage placeholder: UIImage!)
and it is as easy to use as
myImageView.sd_setImageWithURL(NSURL(string:image), placeholderImage:UIImage(named:"qwerty"))
make sure to reset you imageView in tableView delegate cellforRowAtIndexpath method by setting imageview image to nil
myImageView.image = nil
//now set image in imageView
myImageView.sd_setImageWithURL(NSURL(string:image), placeholderImage:UIImage(named:"qwerty"))
this avoids the image duplicating and weird behave of images as imageview of every cell is being reset before reusing.
Github link -> https://github.com/rs/SDWebImage
You have to use multithreading.Only UI is set in main thread, downloading image in background is in another thread.By this way you can solve your problem.
Try SDWebImage library it will save images in catch automatically and your tableView will work smoothly.
Github link -> https://github.com/rs/SDWebImage
Install pod:
platform :ios, '7.0'
pod 'SDWebImage', '~>3.8'
Just import SDWebImage like:
#import SDWebImage
And use like this:
imageView.sd_setImage(with: URL(string: "http://www.example.com/path/to/image.jpg"), placeholderImage: UIImage(named: "placeholder.png"))
I used it in many live projects and it works like a charm :)
Use this extension to cache your images, and also don't forget to update any UI on the main thread.
let imageCache = NSCache<NSString, UIImage>()
extension UIImageView {
func loadImageUsingCacheWithURLString(_ URLString: String, placeHolder: UIImage?) {
self.image = nil
if let cachedImage = imageCache.object(forKey: NSString(string: URLString)) {
self.image = cachedImage
return
}
if let url = URL(string: URLString) {
URLSession.shared.dataTask(with: url, completionHandler: { (data, response, error) in
//print("RESPONSE FROM API: \(response)")
if error != nil {
print("ERROR LOADING IMAGES FROM URL: \(error)")
DispatchQueue.main.async {
self.image = placeHolder
}
return
}
DispatchQueue.main.async {
if let data = data {
if let downloadedImage = UIImage(data: data) {
imageCache.setObject(downloadedImage, forKey: NSString(string: URLString))
self.image = downloadedImage
}
}
}
}).resume()
}
}
}

swift downloading url and populating uiimageview

I'm having difficulty getting each image of my tableviewcell to show the thumbnail of my blog. the link is valid as i had it print to screen to make sure it works here is the block of code causing my brain aneurysm
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath)
let blogPost: BlogPost = blogPosts[indexPath.row]
//cell.textLabel?.text = blogPost.postTitle
postImageLink = blogPost.postImageLink
cell.textLabel?.text = blogPost.postImageLink
if postImageLink != "" && cell.imageView?.image != nil {
dispatch_async(dispatch_get_global_queue( DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
cell.imageView?.image = UIImage(data: NSData(contentsOfURL: NSURL(string:self.postImageLink)!)!)
}
} else {
cell.imageView?.image = UIImage(named: "IMG_0079")
}
return cell
}
i set the text label to postimagelink only to ensure it was parsing the correct code for future usage.
To be clear the problem is the thumbnails of my blog won't load. Only the preset image incase a thumbnail isn't present..postImageLink is a unique url for each thumbnail that I parsed.
(yeah i know i can write a parser but can't solve this problem =(..)
any help is appreciated before I throw my laptop off the roof thanks!
here is image
-rookieprogrammer
try to make two blocks instead, to retrieve and set the image
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
// retrieve image from url
let image = UIImage(data: NSData(contentsOfURL: NSURL(string:self.postImageLink)!)!)
dispatch_async(dispatch_get_main_queue(), { Void in
// set image in main thread
cell.imageView?.image = image
})
}
can read more here
Your code is working. It's just that should be doing it in the main_queue, UI main thread, to edit any UI objects.
dispatch_async(dispatch_get_main_queue()) {
() -> Void in
// retrieve image from url
let image = UIImage(data: NSData(contentsOfURL:NSURL(string:self.postImageLink)!)!)
cell.imageView?.image = image
}

Resources