Hi i am making an application in Xcode and using swift for that. I am downloading images from Firebase and show them in the table view. There are some problems with that. But first i will show the code.
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "customCell", for: indexPath) as! FrontViewCell
cell.contentView.backgroundColor = UIColor.clear
//let whiteRoundedView : UIView = UIView(frame: CGRect(10, 8, self.view.frame.size.width - 20, 149))
let whiteRoundedView: UIView = UIView(frame: CGRect(x: 10, y: 8, width: self.view.frame.width - 20, height: 200))
whiteRoundedView.layer.backgroundColor = CGColor(colorSpace: CGColorSpaceCreateDeviceRGB(), components: [1.0, 1.0, 1.0, 0.8])
whiteRoundedView.layer.masksToBounds = false
whiteRoundedView.layer.cornerRadius = 2.0
whiteRoundedView.layer.shadowOffset = CGSize(width: -1, height: 1)
whiteRoundedView.layer.shadowOpacity = 0.2
cell.contentView.addSubview(whiteRoundedView)
cell.contentView.sendSubview(toBack: whiteRoundedView)
//cell.categoryImageView.image = catImages[indexPath.row]
//print("Product \(allCats[indexPath.row].name)")
cell.categoryLabel.text = allCats[indexPath.row].name
if let n = allCats[indexPath.row].name{
con?.storage?.reference(withPath: "categories/\(n).png").data(withMaxSize: 10 * 1024 * 1024, completion: {
data, error in
if error == nil{
let im = UIImage(data: data!)
cell.categoryImageView.image = im
cell.layoutSubviews()
}
else{
print("Error Downloading Image \(error?.localizedDescription)")
}
})
}
return cell
}
So above is the code to set the images to an imageView in the cell.
Problems
When i scroll down and then scroll up again, the images are different in the same cells.
The tableview scrolling is very laggy.
These are the problems. Please let me know how can i solve this?
I know of a library SDWebImage but i don't know how to download Firebase image with that library. Please help me through this problem. I am very exhausted by this problem. I have been trying to solve it for the last 20 hours without sleep but could not. Please let me know what i am doing wrong and how should i fix that. Thanks.
TableView is laggy because you are redownloading images all the time.
This is a caching issue.
As for the images being different in the same cell, you can change this just by resseting the image to nil, because cells are being reused, they are using a previous image, while the new one downloads.
But both of these issues would be fixed if you were to use some caching framework, for example, probably the best one out there is SDWebImage.
If you don't wanna use a library for this. Here is the most basic implementation of caching images.
let imageCache = NSCache<AnyObject, AnyObject>()
extension UIImageView {
func loadImageUsingCacheWithUrlString(_ urlString: String) {
self.image = nil
//check cache for image
if let cachedImage = imageCache.object(forKey: urlString as AnyObject) as? UIImage {
self.image = cachedImage
return
}
//otherwise start the download
let url = URL(string: urlString)
URLSession.shared.dataTask(with: url!, completionHandler: { (data, response, error) in
//there was an error with the download
if error != nil {
print(error ?? "")
return
}
DispatchQueue.main.async(execute: {
if let downloadedImage = UIImage(data: data!) {
imageCache.setObject(downloadedImage, forKey: urlString as AnyObject)
self.image = downloadedImage
}
})
}).resume()
}
}
Usage:
cell.categoryImageView.loadImageUsingCacheWithUrlString("your firebase url string")
EDIT: Yes, you can use this to download images that are stored in Firebase.
EDIT: This code will solve your issues, but memory management is not considered here, for a serious production app I would suggest looking into libraries dedicated to image caching.
EDIT: I just noticed that there is proper info on Firebase documentation , showing how it works with SDWebImage. Check it out: SDWebImage + Firebase
Related
What is the best way to async call to load a UIImage to a textView as a NSTextAttachment in a tableView? So far this is working very badly.
I am using a URL string to load a single image inside multiple tableView cells.
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell")
//Transform Data From ^ to load at the bottom
tableView.transform = CGAffineTransform (scaleX: 1,y: -1);
cell?.contentView.transform = CGAffineTransform (scaleX: 1,y: -1);
cell?.accessoryView?.transform = CGAffineTransform (scaleX: 1,y: -1);
let username = cell?.viewWithTag(1) as! UITextView
username.text = messageArray[indexPath.row].username
let message = cell?.viewWithTag(2) as! UITextView
//message.text = messageArray[indexPath.row].message // delete later
var test = messageArray[indexPath.row].uploadedPhotoUrl
print(test ?? String.self)
if(test != ""){
// create an NSMutableAttributedString that we'll append everything to
let fullString = NSMutableAttributedString(string: "")
// create our NSTextAttachment
let image1Attachment = NSTextAttachment()
URLSession.shared.dataTask(with: NSURL(string: messageArray[indexPath.row].uploadedPhotoUrl)! as URL, completionHandler: { (data, response, error) -> Void in
if error != nil {
print(error ?? String())
return
}
DispatchQueue.main.async(execute: { () -> Void in
let image = UIImage(data: data!)
image1Attachment.image = image
//calculate new size. (-20 because I want to have a litle space on the right of picture)
let newImageWidth = (message.bounds.size.width - 20 )
//resize this
image1Attachment.bounds = CGRect.init(x: 0, y: 0, width: newImageWidth, height: 200)
// wrap the attachment in its own attributed string so we can append it
let image1String = NSAttributedString(attachment: image1Attachment)
// add the NSTextAttachment wrapper to our full string, then add some more text.
fullString.append(image1String)
fullString.append(NSAttributedString(string: message.text))
// draw the result in a label
message.attributedText = fullString
//message.textStorage.insert(image1String, at: message.selectedRange.location)
message.textColor = .white
test = ""
})
}).resume()
}else {
message.text = messageArray[indexPath.row].message
}
let timeStamp = cell?.viewWithTag(3) as! UILabel
timeStamp.text = messageArray[indexPath.row].timeStamp
let imageView = cell?.viewWithTag(4) as! UIImageView
imageView.image = nil
let urlString = messageArray[indexPath.row].photoUrl
imageView.layer.cornerRadius = 10
imageView.clipsToBounds = true
//Load profile image(on cell) with URL & Alamofire Library
let downloadURL = NSURL(string: urlString!)
imageView.af_setImage(withURL: downloadURL! as URL)
return cell!
}
Images are still lagging when index is scrolling(appearing and disappearing) and are also not loading completely
You are loading an image at a time while the tableviewcell is scrolling. There is some time to call the service and then wait for the response to be returned, thus affecting the scrolling although it is on another thread.
You can try calling the images in batches of maybe 10 or 20 at a time.
TL/DR:
Learn to write better code.
For starters, there's no way tableView(:cellForRowAt:) can accomplish all that work in under 16 milliseconds. I think you should reconsider your app structure and architecture for starters. It would serve your app better to abstract the networking calls to an API that can run on a background thread(s).
One way would be to abstract away a lot of the implementation to anOperationQueue Ray Wenderlich has a couple tutorials on how this works. This particular one was written for Swift 1.2, however the principles for Operation are there.
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.
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
so In my collection view cell I have text and image: This is my code in CollectionViewLayout.
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
if let content = HomeCollectionViewController.posts[indexPath.item].content {
let spaceForPostContentLabel = NSString(string: content).boundingRect(with: CGSize(width: view.frame.width - 32, height: 120), options: NSStringDrawingOptions.usesFontLeading.union(NSStringDrawingOptions.usesLineFragmentOrigin), attributes: [NSFontAttributeName: UIFont.systemFont(ofSize: 15)], context: nil)
if HomeCollectionViewController.posts[indexPath.item].imageURL != nil {
return CGSize(width: view.frame.width, height: spaceForPostContentLabel.height + postImageViewOriginHeight + 168.0)
} else {
return CGSize(width: view.frame.width, height: spaceForPostContentLabel.height + 152.5)
}
} else {
return CGSize(width: view.frame.width, height: 408.5)
}
}
Everything is fine, when it first loaded. But when I scrolled down and scrolled up again, everything is messed up, image is gone and there is a huge blank space where the image should have been existed. Does this have to do with dequeReusableIdentifier?
Note: This error only happen for the first cell, other cell that has image works fine
That's probably happening due to how dequeue works. Even if you have 100 cells, there is a limit for cells loaded at the same time, so after this limit, you start reusing old cells as you scroll.
I already faced the same problem sometimes mainly when using images and the best approach that I found, was to use a cache to the images.
Below I'm posting an example using AlamofireImage to create the cache (but you can use the cache that you prefer, even the builtin cache provided by Swift's library.
import AlamofireImage
let imageCache = AutoPurgingImageCache()
class CustomImageView: UIImageView {
var imageUrlString: String?
func loadImageFromURL(_ urlString: String){
imageUrlString = urlString
let url = URL(string: urlString)
image = nil
if let imageFromCache = imageCache.image(withIdentifier: urlString) {
self.image = imageFromCache
return
}
URLSession.shared.dataTask(with: url!, completionHandler: { (data, response, error) in
if error != nil {
print(error)
return
}
DispatchQueue.main.async(execute: {
let imageToCache = UIImage(data: data!)
if self.imageUrlString == urlString {
self.image = imageToCache
}
imageCache.add(imageToCache!, withIdentifier: urlString)
})
}).resume()
}
}
Basically I'm creating a subclass of UIImageView and adding the image URL as the key to the image that I'm going to keep in the cache. Everytime that I try to load the image from the internet, I check if the image isn't already in the cache, if it is, I set the image to the image in the cache, if not, I load it from the internet asynchronously.
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()
}
}
}