I have used sdwebimage for caching images.
Now imagesArr is array of images i want to display first for all cells.
I am downloading the image in background and then saving into disk so when i want those images in offline mode i can have from the disk.
But in some cases when i scroll the collectionView the images are set incorrect and then again scrolling the images appear to be correct.
This is the below code which i have implemented in cellForItemAtIndexPath. I have tried to debug but seems the data is coming correct and the image name is also correct.
Also the collectionView is laggy in scroll. Please help me to find the issue.
if(imagesArr.count>0)
{
ClothesImage *obj_image=[imagesArr objectAtIndex:0];
NSString *fileName = [stringPath stringByAppendingFormat:#"/%#",obj_image.imagePath];
NSLog(#"FILES NAMES %# ", obj_image.imagePath);
NSData *data = [NSData dataWithContentsOfFile:fileName];
if(data!=nil){
UIImage *image=[UIImage imageWithData:data];
cell.imgProduct.image=image;
}
else{
NSString *offline=[user_defaults objectForKey:#"offline"];
if([offline integerValue]==0){
cell.imgProduct.image=[UIImage imageNamed:#"noImageThumb"];
NSString *str_url=[NSString stringWithFormat:#"%#/%#",WEB_THUMB_IMAGE,obj_image.imagePath];
str_url = [NSString stringWithFormat: #"%#",[str_url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding]];
[cell.imgProduct setImageWithURL:[NSURL URLWithString:str_url] placeholderImage:[UIImage imageNamed:#"noImageThumb"] options: SDWebImageRefreshCached];
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadWithURL:[NSURL URLWithString:str_url] options:0 progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished) {
NSString *stringPathImage = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES)objectAtIndex:0]stringByAppendingPathComponent:#"WebThumb"];
NSData *data = [NSData dataWithData:UIImagePNGRepresentation(image)];
NSString *fileName = [stringPathImage stringByAppendingFormat:#"/%#",obj_image.imagePath];
[data writeToFile:fileName atomically:YES];
NSLog(#"DOWNLOADED IMAGE %#",fileName);
}];
}
else
{
cell.imgProduct.image=[UIImage imageNamed:#"noImageThumb"];
}
}
} else{
cell.imgProduct.image=[UIImage imageNamed:#"noImageThumb"];
}
I think there are a few ways you could improve this.
It seems like you're duplicating a lot of the work that SDWebImage will do for you. That library will cache images and fetch them on demand. You should look at having that library prefetch your files into cache, rather then yourself attempting to prefetch and write them out to disk. Ultimately by the time you are in -cellForRowAtIndexPath, you only need two cases: One for setting an image when the device is offline, and then another that will either fetch or set the image from cache, which SDWebImage provides for you:
[cell.imageView sd_setImageWithURL:[NSURL URLWithString:#"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
That's it. All the caching and switching you're doing isn't necessary. The other thing to be sure of is in your cell subclass method -prepareForReuse, you are nilling out the image.
Related
From my web service i am getting multiple sized images. I m loading them in TableView. When i m loading them new images which haven't cached yet flickers between placeholder and original image, or sometimes the image appear as smaller than the usual size. But just a bit scrolling down or up actually fix the problem but i want them to be at original size from the beginning. But it would appear in original shape from the next time i suppose because the picture was already cached
Initially
After a bit scrolling
My code in cellForRowAtIndexPath:
[cell.image sd_setImageWithURL:[NSURL URLWithString:[NSMutableString stringWithFormat:#"%#app/media/access/pictures?p=%#",baseurl,data.picPath]]
placeholderImage:[UIImage imageNamed: #"image_loader.gif"]];
then in heightForRowAtIndexPath:
NSURL *filePath = [NSURL URLWithString:[NSMutableString stringWithFormat:#"%#app/media/access/pictures?p=%#",baseurl,data.picPath]];
NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:filePath];
UIImage *image = [[SDImageCache sharedImageCache] imageFromDiskCacheForKey:key];
self.img = image;
if (self.img.size.width > CGRectGetWidth(self.view.bounds)) {
CGFloat ratio = self.img.size.height / self.img.size.width;
return CGRectGetWidth(self.view.bounds) * ratio+140;
} else {
return self.img.size.height+180;
}
I have visited https://github.com/rs/SDWebImage and in their common problem section they have mentioned about the problem but the suggested solution didn't work for me!!!
Actually what i did, i checked if the image was cached already and if not i have downloaded it asynchronously under heightForRowAtIndexPath:
if(image != NULL)
{
self.img = image;
}
else
{
SDWebImageDownloader *downloader = [SDWebImageDownloader sharedDownloader];
[downloader downloadImageWithURL:filePath
options:0
progress:^(NSInteger receivedSize, NSInteger expectedSize) {
// progression tracking code
}
completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
if (image && finished) {
// do something with image
self.img = image;
}
}];
}
I hava a uitableview , with custom cell containing two UImages. The logo images are taken from an online website, that's why there's a need to cache the images. Loading the image till now is made like this :
NSURL * imageURL = [NSURL URLWithString:[arra1 objectAtIndex:indexPath.row / 2]];
NSData * imageData = [NSData dataWithContentsOfURL:imageURL];
NSURL * imageURL2 = [NSURL URLWithString:[arra2 objectAtIndex:indexPath.row / 2]];
NSData * imageData2 = [NSData dataWithContentsOfURL:imageURL2];
cell.ima1.image = [UIImage imageWithData:imageData];
cell.ima2.image2 = [UIImage imageWithData:imageData2];
What i learned from searching , is that dataWithContentsOfURL is not asynchronous , and while scrolling it will take a lot of time. I tried several methods but i can't seem to get to right one. This is my first time caching UIImages , i would highly appreciate a detailed explanation with implementation so i could learn aside from getting the job done.
Many Thanks
I use this Library which is just perfect
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:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *MyIdentifier = #"MyIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:MyIdentifier] autorelease];
}
// Here we use the new provided setImageWithURL: method to load the web image
[cell.imageView setImageWithURL:[NSURL URLWithString:#"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
cell.textLabel.text = #"My Text";
return cell;
}
It also cache downloaded images and gives you great performance.
Hope it will help you!
SDWebImage, in my opinion, is the best option.
You simply include it in your app and use it like this:
SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager downloadWithURL:[NSURL URLWithString:image_url]
options:0
progress:nil
completed:^(UIImage *images, NSError *error, SDImageCacheType cacheType, BOOL complete) {
myImageView.image = images;
}] ;
It download images asynchronously, so it does not block UI.
You can check these sample application
LazyTableImages - Sample application from Apple
MonoTouch-LazyTableImages
robertmryan- LazyTableImages - Explains clearly the limitations from apple's sample application.
Hope this helps.
Checkout UIImageLoader https://github.com/gngrwzrd/UIImageLoader
Easy to load an image, and you get callbacks for all the scenarios you would want to handle:
NSURL * imageURL = myURL;
[[UIImageLoader defaultLoader] loadImageWithURL:imageURL \
hasCache:^(UIImage *image, UIImageLoadSource loadedFromSource) {
//there was a cached image available. use that.
self.imageView.image = image;
} sendRequest:^(BOOL didHaveCachedImage) {
//a request is being made for the image.
if(!didHaveCachedImage) {
//there was not a cached image available, set a placeholder or do nothing.
self.loader.hidden = FALSE;
[self.loader startAnimating];
self.imageView.image = [UIImage imageNamed:#"placeholder"];
}
} requestCompleted:^(NSError *error, UIImage *image, UIImageLoadSource loadedFromSource) {
//network request finished.
[self.loader stopAnimating];
self.loader.hidden = TRUE;
if(loadedFromSource == UIImageLoadSourceNetworkToDisk) {
//the image was downloaded and saved to disk.
//since it was downloaded it has been updated since
//last cached version, or is brand new
self.imageView.image = image;
}
}];
I noticed that apps like Intagram uses UICollectionViews to display the feed of photos.
I also noticed that the cells for these photos is somehow placed on screen before the actual photos are downloaded completely. Then when a download completes, that photo is nicely displayed in the correct cell.
I would like to copy that functionality, but I do not know how to go about it.
I am currently downloading a large JSON object which I transform to an array of NSDictionaries. Each NSDictionary contains information about each photo, and among that information, an URL is presented. At this URL, I can find the corresponding image that I need to download and display in my UICollectionViewCells
As for now, I iterate this list and initiate a download for each URL I see. When that download is complete, I reload the collectionview using [self.collectionView reloadData]. But, as you can imagine, if I have 30 cells that all wants an image, there is a lot of reloadData calls.
I am using AFNetworking to handle the download, here is the method, which I call based on the URL I mentioned before:
-(void) downloadFeedImages:(NSString *) photoURL imageDescs:(NSDictionary*)imageDescs photoId:(NSString *)photoID{
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
NSString *directory = [paths objectAtIndex:0];
NSString* foofile = [directory stringByAppendingPathComponent:photoID];
if([[NSFileManager defaultManager] fileExistsAtPath:foofile]){
// IF IMAGE IS CACHED
[self.collectionView reloadData];
return;
}
NSLog(#"photoURL: %#", photoURL);
NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:photoURL]];
AFImageRequestOperation *operation = [AFImageRequestOperation imageRequestOperationWithRequest:request
imageProcessingBlock:nil
success:^(NSURLRequest *request, NSHTTPURLResponse *response, UIImage *image) {
// Save Image
NSLog(#"URL-RESPONSE:%#", image);
NSString *myFile = [directory stringByAppendingPathComponent:photoID];
NSData *imageData = UIImagePNGRepresentation(image);
[imageData writeToFile:myFile atomically: YES];
[self.collectionView reloadData];
}
failure:^(NSURLRequest *request, NSHTTPURLResponse *response, NSError *error) {
NSLog(#"ERROR: %#", [error localizedDescription]);
}];
[[WebAPI sharedInstance] enqueueHTTPRequestOperation:operation];
}
So basically, I wonder how I can achieve the functionality that Instagram and similar applications has when it comes to displaying a feed with images.
In addition, I would like to know a good way to initiate a download for each cell, and when that download is finished, update that cell, not redraw the entire view using [reloadData]
Thanks
The technique you want to implement is called lazy loading. Since you are using AFNetworking it will be easier to implement this in your case. Each of your collection view cell needs to have a UIImageView to display the image. Use the UIImageView+AFNetworking.h category and set the correct image URL by calling method
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
// ....
[cell.imageView setImageWithURL:imageURL placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
// ...
return cell;
}
Placeholder is the image which will be displayed until required image is downloaded. This will simply do the required job for you.
Note: By default, URL requests have a cache policy of NSURLCacheStorageAllowed and a timeout interval of 30 seconds, and are set not handle cookies. To configure URL requests differently, use setImageWithURLRequest:placeholderImage:success:failure:.
Also, for you reference, if you want to implement lazy loading of images yourself, follow this Apple sample code. This is for UITableView but same technique can be used for UICollectionView as well.
Hope that helps!
use
[self.collectionView reloadItemsAtIndexPaths:[NSArray arrayWithObject:indexPath]];
instead of
[self.collectionView reloadData];
here indexPath is the NSIndexPath Object for the corresponding UICollectionViewCell Object
Use following to load image Async in cell in cellForItemAtIndexPath delegate method
let url = URL(string: productModel.productImage)
let data = try? Data(contentsOf: url!)
DispatchQueue.main.async(execute: {
collectionViewCell.imageView.image = UIImage(data: data!)
});
Implement protocol "UICollectionViewDataSourcePrefetching" in you ViewController as
class ViewController: UIViewController , UICollectionViewDataSourcePrefetching {
Set following delegates to your collection view in storyboard (see the attached image)
or programmatically
In ViewController's viewDidLoad method
collectionView.delegate = self
collectionView.dataSource = self
collectionView.prefetchDataSource = self
In my application I am parsing JSON data and then displaying that data in UITableView. The information is displayed in the table but the touch response is lagging really bad. I did some research and found out that it is recommended to implement asynchronous loading for the information and especially the image, but I could not find any relevant solutio that worked with my JSON application. I would appreciate some suggestions and comments of how to sort out this problem, here is the code:
jURL Definition www.website.com/info.json
- (void)viewDidLoad
{
[super viewDidLoad];
dispatch_async(jQueue, ^{
NSData* data = [NSData dataWithContentsOfURL:
jURL];
[self performSelectorOnMainThread:#selector(fetchedData:)
withObject:data waitUntilDone:NO];
});
}
- (void)fetchedData:(NSData *)responseData {
NSError* error;
NSDictionary* jsonDict = [NSJSONSerialization
JSONObjectWithData:responseData
options:kNilOptions
error:&error];
calRes = [jsonDict objectForKey:#"results"];
[self.tableView reloadData];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
NSDictionary *calDict = [calRes objectAtIndex:indexPath.row];
NSURL *imageURL = [NSURL URLWithString:[calDict objectForKey:#"image"]];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage *imageLoad = [[UIImage alloc] initWithData:imageData];
cell.textLabel.text = [calDict objectForKey:#"name"];
cell.detailTextLabel.text = [calDict objectForKey:#"description"];
cell.imageView.image = imageLoad;
return cell;
}
I like to use the AFNetworking library for easy asynchronous image loading. You will need to include the library
#import "AFNetworking.h"
Then use it in the cellForRowAtIndexPath
[cell.imageView setImageWithURL:[NSURL URLWithString:[calDict objectForKey:#"image"]]
placeholderImage:[UIImage imageNamed:#"placeholder"]];
You also need to supply a placeholder image. I use a blank JPG of the size you will want the final image will be.
You can use SDWebImage from this link : https://github.com/rs/SDWebImage
It is the simplest and fastest library for asynchronous image loading.
It also provides image caching.
You can just done the whole operations by simply calling this function:
[cell.imageView setImageWithURL:jURL
placeholderImage:[UIImage imageNamed:#"your place holder here"]];
Just enjoy it.
As a follow up, it will be good to cache the images locally using this very handy plugin by Jake Marsh: JMImageCache
This way, the images will not need to be loaded from the URL the next time you boot the app.
See, the lagging in cellforRowAtIndexPath is because of this
NSURL *imageURL = [NSURL URLWithString:[calDict objectForKey:#"image"]];
NSData *imageData = [NSData dataWithContentsOfURL:imageURL];
UIImage *imageLoad = [[UIImage alloc] initWithData:imageData];
why don't you, use GCD for adding the image? Further more you can have your own NSCache to store the image so that every time the table reloads, the image can be directly loaded from the memory instead of firing the url.
I am a bit desperated about my sectioned tableview. I use a custom UITableViewCell with 4 images like the one below:
I try to load the images via SDWebImage for each cell.
The loading procedures are all done in my custom UITableViewCell - not in the UITableViewController. From the cellForRowAtIndexPath i just call [cell setup] which executes the following code in the current cell:
NSArray *imageViews = [NSArray arrayWithObjects:self.imageView1, self.imageView2, self.imageView3, self.imageView4, nil];
for (int i = 0; i < self.products.count; i++) {
Product *currentProduct = [self.products objectAtIndex:i];
UIImageView *currentImageView = [imageViews objectAtIndex:i];
NSString *thumbURL = [[CommonCode getUnzippedDirectory] stringByAppendingPathComponent:currentProduct.collectionFolderName];
thumbURL = [thumbURL stringByAppendingPathComponent:thumbFolder];
thumbURL = [thumbURL stringByAppendingPathComponent:currentProduct.product_photo];
[currentImageView setContentMode:UIViewContentModeScaleAspectFit];
[currentImageView setImageWithURL:[NSURL fileURLWithPath:thumbURL]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
}
The images are all stored in the documents directory and are not greater than max. 500Kb.
Problem:
My Problem is that when I scroll through my tableview it suddenly crashes and I don't know why. Enabling a symbolic breakpoint for all exceptions shows that it crashes because of one line in SDWebImage. Unfortunately there isn't a debugger output: (It crashes where the image is allocated)
UIImage *image = nil;
if ([imageOrData isKindOfClass:[NSData class]])
{
image = [[UIImage alloc] initWithData:(NSData *)imageOrData];
}
I also tried to load images via dispatch_asnyc with a similar result. Is it possible that it has something to do with concurrent file operations?
Additionally I get Memory Warnings when I scroll very fast so that I have to clear the cache of SDWebImage. At the same time it stops at the code line in SDWebImage mentioned above.
I already searched the web for 2 days now and I didn't find something useful. I would be glad for some hints to fix this problem. If somebody needs additional data such as crash reports or something, just let me know and I will provide them quickly.
I have meet the same problem, But I solve it like below temporarily, but it cause memory problem especially on iPhone 4s
NSArray *arrImgs = cellModel.thumbnails_qqnews;
if (arrImgs.count > 0) {
[self.imgView.subviews enumerateObjectsUsingBlock:^(__kindof UIImageView * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
[obj sd_setImageWithURL:[NSURL URLWithString:arrImgs[idx]] placeholderImage:[UIImage imageNamed:#"placeholdImage"] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
if (image != nil) {
obj.image = [UIImage cutImageWithTargetSize:[NEMulTiNewsCell getImgSize] image:image];
}
}];
}];
}
I think multi images download is illogical,but the method i think is download multi images then draw multi images in one image then display, but it may create large number line of code.