Lazy loading images in UICollectionView with SDWebImageManager - ios

In my method cellForItemAtIndexPath i have this code:
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadWithURL:[NSURL URLWithString:coverURL] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished) {
RRRBigItemCell *cell = (RRRBigItemCell *)[collectionView cellForItemAtIndexPath:indexPath];
if (cell) {
RRRImageView *coverView = (RRRImageView *)[cell viewWithTag:COVER_TAG];
coverView.image = image;
}
}];
When i launch application and i scroll down then everything is ok. But when i scroll up than cell which were already displayed don't have loaded images (cell == nil). And if i again scroll down the problem remains for already displayed cells, only new ones have loaded images.
Any idea what i'm doing wrong?

#pawan is correct, that would be simplest way to implement SDWebImage. It's basically a category of UIImage, so simply use the category methods (setImageWithURL).
A couple of other points since you asked;
In your code, you should be setting the coverImage.image on the main thread, not in the background.
It's good practice to check if the Cell is visible, or there is little point in displaying the content.
It's good practice to create a weak reference to the Collection view when accessing the cell to avoid circular references
so your code ) might be written as follows if you were insistent on doing it the way you have it (Using the category is the best and simplest way)
__weak myCollectionViewController *weakSelf = self;
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadWithURL:[NSURL URLWithString:coverURL] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {
} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished) {
dispatch_async(dispatch_get_main_queue(), ^{
if ([weakSelf.collectionView.indexPathsForVisibleItems containsObject:indexPath]) {
RRRBigItemCell *cell = (RRRBigItemCell *)[weakSelf.collectionView cellForItemAtIndexPath:indexPath];
RRRImageView *coverView = (RRRImageView *)[cell viewWithTag:COVER_TAG];
coverView.image = image;
};
});
}];

why dont you try this
[cell.imageView setImageWithURL:[NSURL URLWithString:#"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
reference SDWebImage

Related

dispatch_async(dispatch_get_main_queue() is not working on iPad, but working on iPhone

I am using SDWebImage library and its working on iPhone. But I don't know why this is not called in iPad. I tried to put break points, but it doesn't hit the break point either. I put this method in cellForItemAtIndexPath.
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString* cellIdentifier = #"CustomCollectionViewCell";
CustomCollectionViewCell* cell = (CustomCollectionViewCell*)[collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
[cell.downloadButton setTag:indexPath.row];
[cell.downloadButton addTarget:self action:#selector(deleteButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
catalogModel = [catalogArray objectAtIndex:indexPath.item];
cell.cellDescription.text = catalogModel.catalogName;
SDWebImageManager *manager = [SDWebImageManager sharedManager];
NSString *urlStr = catalogModel.imageNameThumbnail;
NSURL *url = [NSURL URLWithString:urlStr];
dispatch_async(dispatch_get_main_queue(), ^{
if ([self.catalogCollectionView.indexPathsForVisibleItems containsObject:indexPath])
{
catalogModel = [catalogArray objectAtIndex:indexPath.item];
[manager downloadImageWithURL:[NSURL URLWithString:catalogModel.imageNameThumbnail] options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize) {}completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL)
{
cell.cellImageView.image = image;
NSLog(#"%#",catalogModel.catalogName);
}];
};
});
return cell;
}
There is a problem in concurrent image loading implementation. When -collectionView: cellForItemAtIndexPath: is calling, the device executes code on the main queue. Assuming that -downloadImageWithURL:options:progress:completed: method performs image loading on the background thread and returns instantly we can call it without dispatch_async(dispatch_get_main_queue()... wrapping. Otherwise we cannot guarantee that the completion handler is executing on the main thread, so the code should look like this
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
static NSString* cellIdentifier = #"CustomCollectionViewCell";
CustomCollectionViewCell* cell = (CustomCollectionViewCell*)[collectionView dequeueReusableCellWithReuseIdentifier:cellIdentifier forIndexPath:indexPath];
...
SDWebImageManager *manager = [SDWebImageManager sharedManager];
// methods with completion block usually return instantly
[manager downloadImageWithURL:aURL options:0 progress:^(NSInteger receivedSize, NSInteger expectedSize){} completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
// ensure ui is updating on main thread and for visible cell
dispatch_async(dispatch_get_main_queue(), ^{
if ([collectionView.indexPathsForVisibleItems containsObject:indexPath]) {
cell.cellImageView.image = image;
}
});
}];
return cell;
}
And the different results on iPhone and iPad can be explained by the architectural differences in technical specifications of testing devices.

SDWebImage animate images

I tried over 100 methods to animate images using SDWebImage but always fail. Do I understand good to using blocks? I am having IBOutlet property image names images1. I made array to store values and animate 3 images.
NSMutableArray *imagegallery = [[NSMutableArray alloc] init];
NSLog(#"IMAGES-%#", imagegallery);
[self.images1 setImageWithURL:[NSURL URLWithString:[self.objc objectForKey:#"Img1"]]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType) {
[imagegallery addObject:image];
NSLog(#"Ima1-------%#", image);
}];
[self.images1 setImageWithURL:[NSURL URLWithString:[self.objc objectForKey:#"Img2"]]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType) {
[imagegallery addObject:image];
}];
[self.images1 setImageWithURL:[NSURL URLWithString:[self.objc objectForKey:#"Img3"]]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType) {
[imagegallery addObject:image];
}];
self.images1.animationImages = imagegallery; //this line gets error
self.images1.animationDuration = 0.5;
[self.view addSubview:self.images1];
[self.images1 startAnimating];
I think when you set self.images1.animationImages = imagegallery because your image couldn't be loaded yet, method setImageWithURL:placeholderImage:completed: is an asynchronous function. you should try another way
like:
Animate images in uiimageview
The problem is that imageGallery contains strings as well as the images you add in the completed block. There's no reason to set imageGallery to be a copy of animatee in the first place since you never use those strings in imageGallery. Just create a new empty mutable array for imageGallery.

SDWebImageManager image not loaded into TableViewController

I am using SDWebImageImagemanger to hadnle asyn call and caching. However it does not return the image to the cell.imageview.view. Here is the code:
NSString *imageURLString=[[tableData objectAtIndex:indexPath.row] valueForKey:#"picture"];
NSURL *url = [NSURL URLWithString:imageURLString];
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadWithURL:url
options:0
progress:^(NSInteger receivedSize, NSInteger expectedSize)
{
// progression tracking code
}
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished)
{
if (image)
{
cell.imageView.image = image;
}
}];
You should use it this way:
NSString* imageURL = [[tableData objectAtIndex:indexPath.row] valueForKey:#"picture"];
[cell.imageView setImageWithURL:[NSURL URLWithString:imageURL]];
For that, first import the category:
#import <SDWebImage/UIImageView+WebCache.h>
Please note that you don't use the manager at all, you just use the UIImageView+WebCache category provided by the library, which gives you the setImageWithURL: method. There are other variants of the method which allows you to keep track of the download process, a completion handler, etc.

UIImageView+AFNetworking - need to scroll UICollectionView to set image

I'm using a UICollectionView and want to display images fetched from a server in it's cells. To do this I'm using UIImageView+AFNetworking. In my cellForItemAtIndexPath method I have the following code:
__weak CustomCollectionViewCell *weakCell = cell;
[cell.cellImageView setImageWithURLRequest:request
placeholderImage:placeholderImage
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
weakCell.cellImageView.image = image;
[weakCell setNeedsLayout];
} failure:nil];
The placeholder always show, but sometimes the downloaded images aren't showing. Any ideas on how I could solve this?
I suspect the problem is that you're on a background thread when you try to set the image, whereas you should always perform UI operations on the main thread.
Try setting the image in the main thread with something like
__weak CustomCollectionViewCell *weakCell = cell;
[cell.cellImageView setImageWithURLRequest:request
placeholderImage:placeholderImage
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
dispatch_async(dispatch_get_main_queue(), ^{
weakCell.cellImageView.image = image;
});
} failure:nil];
I'm pretty sure that AFNetworking moves all the block execution on the main thread, I'm just wondering if you are sure that those images are correctly downloaded.
Try to log the image size of the retuned image, and if it is present always use a failure block, maybe just printing the error.
Something like that:
typeof(self) __weak weakCell = cell; //but you don't need in that scenario
[cell.cellImageView setImageWithURLRequest:request
placeholderImage:placeholderImage
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
NSLog(#"Image %# scale %f", NSStringFromCGSize(image.size), image.scale);
weakCell.cellImageView.image = image;
[weakCell setNeedsLayout];
} failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
NSLog(#"Error %#",error.localizedDescription);
}];
Solution for you question
SDWebImage
You just need to #import <SDWebImage/UIImageView+WebCache.h> to your project, and you can define also the placeholder when image is being downloaded with just this code:
-(UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
MyCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"MyCell"
forIndexPath:indexPath];
DLog(#"url at index %d : %#", indexPath.row, ((MyObject *)[self.list.array objectAtIndex:indexPath.row]).imageUrl);
MyObject *object = (MyObject *)[self.list.array objectAtIndex:indexPath.row];
[cell.imageView setImageWithURL:[NSURL URLWithString:#"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
return cell;
}
It also cache downloaded images and gives you great performance.
Hope it will help you!
After downloading the images, I replaced
[self.myCollectionView reloadData]
with
[self.myCollectionView reloadSections:[NSIndexSet indexSetWithIndex:0]];
then refresh and it works well, you can try it.

SDWebImage With Custom UITableView Cell Image

I am trying to use SDWebImage library with a custom UITableViewCell. Below is the method that is downloading the images from the web service:
- (void) downloadThumbnails:(NSURL *)finalUrl
{
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadWithURL:finalUrl
options:0
progress:nil
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished)
{
if (image)
{
// do something with image
// THERE I HAVE TO ASSIGN the property "thumbnail" with the "image"
// So it can be used by the tableview controller class
}
}];
}
The above code is in a separate file named RSSItem. While my UITableViewController class has the following cellForRowAtIndexPath method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"ItemsCell";
ItemsViewCell *cell = (ItemsViewCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
RSSItem *item = [[channel items] objectAtIndex:[indexPath row]];
cell.titleLabel.text = [item title];
cell.creatorLabel.text = [item creator];
cell.pubTimeLabel.text = [item pubTime];
cell.thumbContainer.image = [item thumbnail];
return cell;
}
Can somebody point out how can i configure the if (image) inside downloadThumbnails method? I just need to assign "image" to the property "thumbnail", how can i do that?
Looks like you have already used the right method to download images asynchronously using SDWebImage. All you need to do is to set the thumbnail property to the "image". Here is how you can do it:
- (void) downloadThumbnails:(NSURL *)finalUrl
{
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadWithURL:finalUrl
options:0
progress:nil
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished)
{
if (image)
{
[self setThumbnail:image];
}
}];
}

Resources