How to Implement Lazy Loading of Images in Table View Using Swift

how to implement lazy loading of images in table view using swift

Old Solution:

Since you doesn't show any code.

Here is the example for you.

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

// try to reuse cell
let cell:CustomCell = tableView.dequeueReusableCellWithIdentifier("DealCell") as CustomCell

// get the deal image
let currentImage = deals[indexPath.row].imageID
let unwrappedImage = currentImage
var image = self.imageCache[unwrappedImage]
let imageUrl = NSURL(string: "http://staging.api.cheapeat.com.au/deals/\(unwrappedImage)/photo")

// reset reused cell image to placeholder
cell.dealImage.image = UIImage(named: "placeholder")

// async image
if image == nil {

let request: NSURLRequest = NSURLRequest(URL: imageUrl!)

NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue(), completionHandler: {(response: NSURLResponse!,data: NSData!,error: NSError!) -> Void in
if error == nil {

image = UIImage(data: data)

self.imageCache[unwrappedImage] = image
dispatch_async(dispatch_get_main_queue(), {
cell.dealImage.image = image

})
}
else {

}
})
}

else{
cell.dealImage.image = image
}

return cell

}

Follow THIS tutorial for more Info. Hope this will help you.

New Solution:

Here is extension for it which is created by my friend Leo Dabus which is really simple to use:

extension UIImageView {
func downloadImageFrom(link link:String, contentMode: UIViewContentMode) {
NSURLSession.sharedSession().dataTaskWithURL( NSURL(string:link)!, completionHandler: {
(data, response, error) -> Void in
dispatch_async(dispatch_get_main_queue()) {
self.contentMode = contentMode
if let data = data { self.image = UIImage(data: data) }
}
}).resume()
}
}

Now in your cellForRowAtIndexPath method assign image to cell this way:

cell.cellImageView.image = UIImage(named: "placeholder")  //set placeholder image first.
cell.cellImageView.downloadImageFrom(link: imageLinkArray[indexPath.row], contentMode: UIViewContentMode.ScaleAspectFit) //set your image from link array.

And as Rob suggested into comment here is some useful libraries which you can use:

  1. https://github.com/Alamofire/AlamofireImage
  2. https://github.com/onevcat/Kingfisher
  3. https://github.com/rs/SDWebImage
  4. https://github.com/kean/DFImageManager

iOS lazy-loading of table images

You can achieve lazy loading in your tableview by following these steps :

In your Class.h, put:

NSMutableDictionary *Dict_name;
BOOL isDragging_msg, isDecliring_msg;

Now In Class.m file, put this code in view did load:

Dict_name = [[NSMutableDictionary alloc] init];

In cellForRowAtIndexPath:

if ([dicImages_msg valueForKey:[[msg_array objectAtIndex:indexPath.row] valueForKey:@"image name or image link"]]) { 
cell.image_profile.image=[dicImages_msg valueForKey:[[msg_array objectAtIndex:indexPath.row] valueForKey:@"image name or image link"]];
}
else
{
if (!isDragging_msg && !isDecliring_msg)
{
[dicImages_msg setObject:[UIImage imageNamed:@"Placeholder.png"] forKey:[[msg_array objectAtIndex:indexPath.row] valueForKey:@"image name or image link"]];
[self performSelectorInBackground:@selector(downloadImage_3:) withObject:indexPath];
}
else
{
cell.image_profile.image=[UIImage imageNamed:@"Placeholder.png"];
}
}

And for the download image the function is:

-(void)downloadImage_3:(NSIndexPath *)path{
NSAutoreleasePool *pl = [[NSAutoreleasePool alloc] init];

NSString *str=[here Your image link for download];

UIImage *img = [[UIImage alloc] initWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:str]]];

[dicImages_msg setObject:img forKey:[[msg_array objectAtIndex:path.row] valueForKey:@"image name or image link same as cell for row"]];

[tableview performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO];

[pl release];
}

And at last put these methods in your class:

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
isDragging_msg = FALSE;
[tableview reloadData];
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
isDecliring_msg = FALSE;
[tableview reloadData];
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
isDragging_msg = TRUE;
}
- (void)scrollViewWillBeginDecelerating:(UIScrollView *)scrollView{
isDecliring_msg = TRUE;
}

how to load images fast during scrolling in swift 3?

You can use HanekeSwift. It automatically manages caching so your view won't block while loading heavy Images. https://github.com/Haneke/HanekeSwift

Hope this helps!

UITableView On Screen Image Download Lazy Loading

I think what you have to do is:

  1. display some placeholder image in your table cell while the image is being downloaded (otherwise your table will look empty);

  2. when the downloaded image is there, send a refresh message to your table.

For 2, you have two approaches:

  1. easy one: send reloadData to your table view (and check performance of your app);

  2. send your table view the message:

    - (void)reloadRowsAtIndexPaths:(NSArray *)indexPaths withRowAnimation:(UITableViewRowAnimation)animation

Using reloadRowsAtIndexPaths is much better, but it will require you to keep track of which image is associated to which table row.

Keep in mind that if you use Core Data to store your images, then this workflow would be made much much easier by integrating NSFetchedResultController with your table view. See here for an example.

Again another approach would be using KVO:

  1. declare this observe method in ItemsViewCell:

    - (void)observeValueForKeyPath:(NSString *)keyPath
    ofObject:(id)object
    change:(NSDictionary *)change
    context:(void *)context {

    if ([keyPath isEqual:@"thumbnail"]) {
    UIImage* newImage = [change objectForKey:NSKeyValueChangeNewKey];
    if (newImage != (id)[NSNull null]) {
    self.thumbContainer.image = newImage;
    [self.thumbContainer setNeedsLayout];
    }
    }

    }
  2. then, when you configure the cell do:

    RSSItem *item = [[channel items] objectAtIndex:[indexPath row]];
    cell.titleLabel.text = [item title];
    cell.thumbContainer.image = [item thumbnail];

    [item addObserver:cell forKeyPath:@"thumbnail" options:NSKeyValueObservingOptionNew context:NULL];

By doing this, cell will be notified whenever the given item "thumbnail" keypath changes.

Another necessary change is doing the assignment like this:

       self.thumbnail = [UIImage imageWithData:tempData];

(i.e., using self.).

ANOTHER EDIT:

I wanted to download and load the images just like in the LazyTableImages example by Apple. When its not decelerating and dragging, then only onscreen images are loaded, not all images are loaded at once.

I suspect we are talking different problems here.

I thought your issue here was that the downloaded images were not displayed correctly (if you do not scroll the table). This is what I understand from your question and my suggestion fixes that issue.

As to lazy loading, there is some kind of mismatch between what you are doing (downloading the whole feed and then archiving it as a whole) and what you would like (lazy loading). The two things do not match together, so you should rethink what you are doing.

Besides this, if you want lazy loading of images, you could follow these steps:

  1. do not load the image in parser:foundCDATA:, just store the image URL;

  2. start downloading the image in tableView:cellForRowAtIndexPath: (if you know the URL, you can use dataWithContentOfURL as you are doing on a separate thread);

  3. the code I posted above will make the table update when the image is there;

  4. at first, do not worry about scrolling/dragging, just make 1-2-3 work;

  5. once it works, use the scrolling/dragging delegate to prevent the image from being downloaded (point 2) during scrolling/dragging; you can add a flag to your table view and make tableView:cellForRowAtIndexPath: download the image only if the flag says "no scrolling/dragging".

I hope this is enough for you to get to the end result. I will not write code for this, since it is pretty trivial.

PS: if you lazy load the images, your feed will be stored on disk without the images; you could as well remove the CGD group and CGD wait. as I said, there is not way out of this: you cannot do lazy loading and at the same time archive the images with the feed (unless each time you get a new image you archive the whole feed). you should find another way to cache the images.

what should I do to load asynchronous images in tableView?

I think you have 2 options

  • You download image async when cell visible ( I recommend )

  • Download all images and show cell visible

If you are download all images increase your memory usage of app and if too much usage it, iOS will crash your app.

First path:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
if let logo = arrFavServices[indexPath.section]?.logo {
// We need download image here
cell.imageView?.downloaded(from: logo, contentMode: .center)
}

// Configure the cell...
if let color = arrFavServices[indexPath.section]?.color {
cell.backgroundColor = UIColor(hexString: color)
}
return cell
}

Second Path:

You can use dispatch group. UITableView is waiting for download all images.

// Cache array
var downloadedImages: [UIImage] = []

// Create an instance
var dispatchGroup = DispatchGroup()


func loadImages() {

// Every tick of loop, we enter the group
for img in arrFavServices {

// Join the group
dispatchGroup.enter()
if let url = img?.logo {
let imgDownload = UIImageView()
imgDownload.downloaded(from: url, contentMode: .redraw, completion: { [weak self] downloadedImage in
guard let self = self else { return }
self.downloadedImages.append(downloadedImage)

// And leave group when task is done
dispatchGroup.leave()

})
} else {
let imgDownload = UIImageView()
imgDownload.image = UIImage(named: "logo")
arrImages.append(imgDownload)

// We can leave here too because we add image to array
dispatchGroup.leave()
}

}

// We have to listen group, and that we update tableView or UI todo
dispatchGroup.notify(queue: .main) {
self.tableView.reloadData()
self.tableView.layoutIfNeeded()
self.tableView.layoutSubviews()
self.utilActivityIndicator.shared.hideLoader(view: self.view)
}
}

You can set completion handler like below

extension UIImageView {
func downloaded(from url: URL, contentMode mode: UIView.ContentMode = .scaleAspectFit, completion: ((UIImage) -> Void)?) {
contentMode = mode
URLSession.shared.dataTask(with: url) { data, response, error in
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let mimeType = response?.mimeType, mimeType.hasPrefix("image"),
let data = data, error == nil,
let image = UIImage(data: data)
else { return }
DispatchQueue.main.async() {
completion?(image)
}
}.resume()
}
func downloaded(from link: String, contentMode mode: UIView.ContentMode = .scaleAspectFit, completion: ((UIImage) -> Void)?) {
guard let url = URL(string: link) else { return }
downloaded(from: url, contentMode: mode, completion: completion)
}
}

UITableView with infite scrolling and lazy loading

I'm not sure if I totally grok the fullness of your question, but here are some things that I did when confronted with a similar problem.

  • I used -tableView:cellForRowAtIndexPath: as my cue to load more data from the web. I picked a number (in my case 10), when indexPath.row + 10 >= self.data.count load more data.

    • I needed to call -tableView:cellForRowAtIndexPath: anyway.
    • I didn't force a callback into the tighter loop of -scrollViewDidScroll:.
  • I subclassed UIImageView with a class that would async load images which I called URLImageView.

    • In -tableView:cellForRowAtIndexPath:, I assigned a URL to the URLImageView instance.
    • That assignment caused a background operation that would either load from disk or load from the web.
    • The operation was cancelable, so I didn't keep working on an image that no longer needed loading.

I had a table view with hundreds (but not thousands) of images. I only had 2 or 3 table cells on the screen at once.

  • I used -refreshData: it worked like a champ.
  • I even got URLImageView to fade in the image. It was a very nice effect.

I hope that helps.



Related Topics



Leave a reply



Submit