Photos in tableView from local gallery uses too much RAM - ios

When I try to get photos using a URL from a local gallery I add them to a tableView using ALAssetsLibrary()
My code:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: AllPhotosTableViewCell!
cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! AllPhotosTableViewCell
let url = array[indexPath.section]["photos"] as! [AnyObject]
if let image = cachedImages[url[indexPath.row] as! String] {
cell.imageViewImg.image = imageResize(image, sizeChange: CGSize(width: 50, height: 50))
} else {
let nsURL = NSURL(string: url[indexPath.row] as! String)!
var loadError: NSError?
asset.assetForURL(nsURL, resultBlock: { (asset) -> Void in
if let ast = asset {
let image = UIImage(CGImage: ast.defaultRepresentation().fullResolutionImage().takeUnretainedValue())
self.cachedImages[url[indexPath.row] as! String] = image
dispatch_async(dispatch_get_main_queue(), {
cell.imageViewImg.image = self.imageResize(image, sizeChange: CGSize(width: 50, height: 50))
})
}
}, failureBlock: {(error) -> Void in
loadError = error
})
}
return cell
}
I try to minimize the size of photos, it helps a little but it doesn't solve the problem if I need to use many photos (10 or more...)

What is self.cachedImages. If that's a dictionary then your code is holding ALL of your images in memory, and as you scroll your memory footprint will grow and grow. The simplest way to fix that would be to get rid of the cache dictionary entirely and load the images from the asset library every time, but that might be slow.
If you are scaling your images to display them in a table view then load the asset, open a graphics context at the target size, render the image at your thumbnail size, and cache the thumbnail image.
You might also look at using NSCache, which acts like a dictionary but manages flushing objects when memory gets low.

Related

Swift: How to increase loading speed of images from documents?

My app saved photo to the local documents folder and I used the UICollectionView to display all the image from that folder. But whenever I try to open the CollectionView it often took several seconds to open. I'm thinking that maybe the image files are too big, each photo is around 10MB. I also tried using thumbnails to display in collectionview but it still too slow. Any idea how to speed that up?
override func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: reuseIdentifier, for: indexPath) as! SPCell
// Configure the cell
cell.imageView.image = loadImage(fileName: self.fileURLs[indexPath.row])
return cell
}
func loadImagesFromDocuments(){
let fileManager = FileManager.default
let documentsURL = NSHomeDirectory() + "/Documents/Secure/"
do {
fileURLs = try fileManager.contentsOfDirectory(at: URL(string: documentsURL)!, includingPropertiesForKeys: nil)
} catch {
print("Error while enumerating files : \(error.localizedDescription)")
}
}
func loadImage(fileName: URL) -> UIImage? {
do {
let imageData = try Data(contentsOf: fileName)
return UIImage(data: imageData)
} catch {
print("Error loading image : \(error)")
}
return nil
}
Current problem is that you load the image every time the cell appears so Instead of
var fileURLs = [URL]()
Make it
var fileImages = [UIImage]()
Then inside viewDidLoad
fileImages = fileURLs.compactMap { self.loadImage(fileName: $0) }
you are synchronously loading images while returning every cell at indexPath.
cell.imageView.image = loadImage(fileName: self.fileURLs[indexPath.row])
Instead, you can create a custom UICollectionViewCell implementation with a variable in global scope say imageURL.
after cell initialization, do this:
cell.imageURL = self.fileURLs[indexPath.row]
and, in ViewDidLoad() of your custom class, add this line:
self.imageView.image = loadImage(fileName: self.imageURL)
doing so, will lead to images being loaded in each custom implementation of cell unblocking the thread which is invoking the DataSource of your CollectionView.

Load Images From Fiverr in TableView

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

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

why is my Collectionview cell.image is gone and messed up?

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.

Swift - Adding image to tableview cell from asset library

I am trying to add an image to a tableview cell but not having much luck getting it to display.
The image is being loaded from the file system (I println() the result) as a UIImage but I cannot seem to get it into the cell. Placing a println() after the closure shows me that the images are all loaded after the cell has been returned.
Here is my code:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! UITableViewCell
let note = notes[indexPath.row]
let nsURL = NSURL(string: note.valueForKey("url") as! String)!
var loadError: NSError?
var image: UIImage?
assetsLibrary!.assetForURL(nsURL, resultBlock: { (asset) -> Void in
if let ast = asset {
let iref = ast.defaultRepresentation().fullResolutionImage().takeUnretainedValue()
image = UIImage(CGImage: iref)
println("The loaded image: \(image)")
}
}, failureBlock: {(error) -> Void in
loadError = error
})
cell.textLabel!.text = note.valueForKey("title") as? String
cell.imageView!.image = image
return cell
}
When I replace the closure with the following to load a image from the project itself it shows in the table. This leads me to believe it not due to an issue with the way the story board is set up.
UIImage(named: transportItems[indexPath.row])
Any help would be much appreciated.
It doesn't work that way. cellForRowAtPathIndex is a synchronous routine. assetForURL is an asynchronous routine. It will return the data long time after cellForRowAtPathIndex has returned.
Here's what you should do: Have a method cachedAssetForURL which returns the asset immediately, or returns nil. If it returns an asset, store it. Remember this has to be as efficient as possible, because this is called while the user scrolls up and down through the images.
If the method returns nil, trigger a download in the background. When that download finishes, don't even try to store the image in the cell - by this time, the same cell could display an entirely different object! Instead store the data so that cachedAssetForURL will be able to return the asset, and invalidate the row of your table view.
Fire a notification in the block (it's asynchronous), manage it by setting your imageview and reload your view.
The underlying issue I was encountering, as pointed out by #gnasher729, was the mishmash of synchronous and asynchronous calls. Rewriting the code to take this into account and to include a cache, I have the following working solution.
var cachedImages = [String:UIImage]()
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as! UITableViewCell
let note = notes[indexPath.row]
let url = note.valueForKey("url") as! String
cell.textLabel!.text = note.valueForKey("title") as? String
cell.imageView!.image = UIImage(named: "note-icon")
if let image = cachedImages[url]{
cell.imageView!.image = image
} else {
let nsURL = NSURL(string: url)!
var loadError: NSError?
assetsLibrary!.assetForURL(nsURL, resultBlock: { (asset) -> Void in
if let ast = asset {
let image = UIImage(CGImage: ast.defaultRepresentation().fullResolutionImage().takeUnretainedValue())
self.cachedImages[url] = image
dispatch_async(dispatch_get_main_queue(), {
if let cellToUpdate = tableView.cellForRowAtIndexPath(indexPath) {
cellToUpdate.imageView?.image = image
}
})
}
},failureBlock: {(error) -> Void in
loadError = error
})
}
return cell
}
This could be further improved to take more off of the main thread.

Resources