I have a table view that loads title and images from url address. Since I added the images, the scrolling isn't smooth. I have changed the background to clear. The images are low resolution. I prefer accessing them with url, not to downloaded the image and re-upload it on the app. Looking forward for your solutions to make the scrolling smooth. Thanks
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TableCell *cell = [tableView dequeueReusableCellWithIdentifier:#"TableCell" forIndexPath:indexPath];
NSString *str = [[feeds objectAtIndex:indexPath.row] objectForKey: #"title"];
[[cell textLabel] setNumberOfLines:0]; // unlimited number of lines
[[cell textLabel] setFont:[UIFont systemFontOfSize: 16.0]];
cell.backgroundColor = [UIColor clearColor];
cell.contentView.backgroundColor = [UIColor clearColor];
cell.TitleLabel.text=str;
UIImage *pImage=[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:feeds2[indexPath.row]]]];;
[cell.ThumbImage setImage:pImage];
return cell;
}
replace your code in cellForRowAtIndexPath:
after this line cell.TitleLabel.text=str;
That way you load each image in the background and as soon as its loaded the corresponding cell is updated on the mainThread.
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^(void) {
NSData *imgData = NSData *imgData = [NSData dataWithContentsOfURL:[NSURL URLWithString:feeds2[indexPath.row]]];
if (imgData) {
UIImage *image = [UIImage imageWithData:imgData];
dispatch_sync(dispatch_get_main_queue(), ^(void) {
UIImage *image = [UIImage imageWithData:imgData];
if (image) {
cell.ThumbImage.image = image;
}
});
});
A better approach is to cache the image , so you dont need to download them each time , the table scroll.
here are some very good references to accomplish this.
LazyTableImages Reference
SDWebImage
UIImageView+AFNetworking
Answer is simply you have to implement the loading with NSOperation where a custom class to handle your download and have your NSOperationQueue as downloadQueue. Every UITableView is a (sub class) UIScrollView therefore you can use the methods directly.
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
[downloadQueue cancelAllOperations]; // clear your queue
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
if (!decelerate) {
// start download only for visible cells
}
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView {
// start download only for visible cells
}
for more detail information visit this tutorial. There you can really find good solution for your need.
Related
In UITableviewcell I have many Image view which will load the image from url
I want to store the image in cache so that when I scroll again it should not load from url
My Code is
UIActivityIndicatorView *indicator = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
[indicator startAnimating];
[indicator setCenter:cell.image_VW.center];
[cell.contentView addSubview:indicator];
NSString *url_STR = [NSString stringWithFormat:#"%#news/%#",IMAGE_URL,[tmp_NEWS valueForKey:#"image"]];
NSString *url_STR = [NSString stringWithFormat:#"%#news/%#",IMAGE_URL,[tmp_NEWS valueForKey:#"image"]];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
//retrive image on global queue
UIImage * img = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:url_STR]]];
dispatch_async(dispatch_get_main_queue(), ^{
[indicator removeFromSuperview];
NEWS_BIG_CELL * cell = (NEWS_BIG_CELL *)[self.tbl_CONTENTS cellForRowAtIndexPath:indexPath];
//assign cell image on main thread
//cell.image_VW.contentMode = UIViewContentModeScaleAspectFit;
cell.image_VW.image = img;
});
});
you should use NSCache, where you can save you images by keys and get them if they are exist in cache (by key), please see this swift example :
http://www.splinter.com.au/2015/09/24/swift-image-cache/
or use:
https://github.com/rs/SDWebImage
For Simple solution is SDWebImage for caching image gitHub link
in your UITableviewcell cellForRowAtIndexPath method
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"MyIdentifier"];
[cell.image_VW sd_setImageWithURL:[NSURL URLWithString:#"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
return cell;
}
First you need to check if the data is not nil from dataWithContentsOfURL, then if the check is true:
you can save the image as NSData in NSUserdefaults with its URL as the key (setObject:(NSData) forKey:(URL as NSString)) of that preference, so that next time, you can check in the cache if any data exists for the URL to display it immediately without getting it again from the internet (objectForKey:(URL as NSString)).
im making a tableview loaded with some NSArrays, the cell contains two labels and a background image view loaded with a URL image. The problem is that the scrolling of the tableview is slow, its like freeze or something.... i think that is because of the Imageview but what can i do???
heres some of my code so you can see:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return _Title.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
MyCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
int row = [indexPath row];
cell.TitleLabel.text = _Title[row];
cell.DescriptionLabel.text = _content[row];
cell.CellImageView.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:_ImageUrl[row]]]];
cell.CellImageView.contentMode = UIViewContentModeTop;
cell.CellImageView.contentMode = UIContentSizeCategoryMedium;
cell.backgroundColor = [UIColor clearColor];
return cell;
}
For extra info this tableview presents a Detailviewcontroller with this same info.
Load the image asynchronously(load it as the images come in). Look into great classes such as SDWebImage https://github.com/rs/SDWebImage
You are blocking the main thread with the following line of code:
cell.CellImageView.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:_ImageUrl[row]]]];
My suggestion would be to use AFNetworking and replace the code with the following:
[cell.cellImageView setImageWithURL:[NSURL URLWithString:#"http://example.com/image.png"]];
Also, your pointers should start with a lower case letter. For instance, CellImageView should be cellImageView.
You are calling [NSData dataWithContentsOfURL:[NSURL URLWithString:_ImageUrl[row]]] from cellForRowAtIndexPath , which is not good idea. Because it'll go to the server every time a UITableViewCell is loaded.
You must use Asynchronous image loading & cache. These libraries might help you : (My personal favourite is SDWebImage)
1) https://github.com/rs/SDWebImage
2) https://github.com/nicklockwood/AsyncImageView
And more, you can refer to the sample code by Apple about LazyTableImages - https://developer.apple.com/library/ios/samplecode/LazyTableImages/Introduction/Intro.html
UPDATE:
For SDWebImage follow this guide. It's very good.
http://iosmadesimple.blogspot.in/2013/04/lazy-image-loading.html
To expand on #vborra's answer - the reason why your tableview is slow is that (in your code) the entire image MUST have finished downloading before it displays.
This is because dataWithContentsOfURL: is a synchronous method. You need to use asynchronous APIs to download images in the background and when they've finished downloading, display them on the screen.
Here is a code snippet from the github page adapted for your example. Make sure you add the folder SDWebImage and it's contents from https://github.com/rs/SDWebImage to your project.
#import <SDWebImage/UIImageView+WebCache.h>
...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
MyCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
int row = [indexPath row];
cell.TitleLabel.text = _Title[row];
cell.DescriptionLabel.text = _content[row];
[cell.CellImageView setImageWithURL:[NSURL URLWithString:_ImageUrl[row]]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
cell.CellImageView.contentMode = UIViewContentModeTop;
cell.CellImageView.contentMode = UIContentSizeCategoryMedium;
cell.backgroundColor = [UIColor clearColor];
return cell;
}
Note, if you're downloading images of all different sizes, you may end up with resizing issues which will also decrease your performance. The SDWebImage page has a link to an article to help you with this problem if you come across it. http://www.wrichards.com/blog/2011/11/sdwebimage-fixed-width-cell-images/
You may also experience a performance bump when using transparency and tableviews, but it depends on the rest of your code.
AFNetworking is an excellent tool to use, but might be overkill if you're not using networking anywhere else in your app.
load your image like this
//get a dispatch queue
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//this will start the image loading in bg
dispatch_async(concurrentQueue, ^{
NSData *imageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:mapUrlStr]];
//this will set the image when loading is finished
dispatch_async(dispatch_get_main_queue(), ^{
cell.CellImageView.image = [UIImage imageWithData:imageData]
});
});
In my Core Data model I have an entity which keeps URLs of images. I want to download these images so I can use them in my UICollectionView model, which is an array of my Core Data entities. I want to always be able to access these images synchronously (assuming they have already been downloaded async) so there's no delay between them loading in their respective cell.
Currently, I am using an async method in the cellForIndexPath: data source delegate method of the UICollectionView but when the collection view is reloaded, if there are images still being downloaded, they get assigned to the wrong cell as cells have been inserted during this time.
This problem seems like it should be very obvious to solve but I cannot work it out.
Any ideas are greatly appreciated.
Thanks.
You can download the images asynchronously in viewDidLoad, and add them to an NSMUtableArray as follows
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
for (int i = 0; i < carPhotos.count; i++) {
NSURL *photoURL = [NSURL URLWithString:carPhotos[i]];
NSData *photoData = [NSData dataWithContentsOfURL:photoURL];
UIImage *image = [UIImage imageWithData:photoData];
[carImages addObject:image];
dispatch_async(dispatch_get_main_queue(), ^{
[self.tableView reloadData];
});
}
});
And then check in the cellForRowAtIndexpath to ensure that the indexPath matches the arrayIndex, and if you want load a dummy image for not-loaded images, as follows:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
CarsAppCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
cell.carMakeLabel.text = carMakes[indexPath.row];
cell.carModelLabel.text = carModels[indexPath.row];
if (carImages.count > indexPath.row) {
cell.carImage.image = carImages[indexPath.row];
} else {
cell.carImage.image = [UIImage imageNamed:#"dummy.png"];
}
return cell;
}
I hope it helps....
A couple of things you can do:
In the cell's prepareForReuse you can cancel any (still) pending image request.
In the completion process for the image request you can verify that the image url is still as expected before setting the image.
Something like:
-(void)prepareForReuse
{
[super prepareForReuse];
_imageUrl = nil;
}
-(void)useImageUrl:(NSString*)imageUrl
{
_imageUrl = imageUrl;
if(_imageUrl)
{
__weak typeof(self) weak = self;
[UIImage fetchRemoteImage:imageUrl completion:^(UIImage* image){
if(weak.imageUrl && [weak.imageUrl isEqualToString:imageUrl] && image) {
weak.imageView.image = image;
}
}];
}
}
I want to load too many image after image processing each image.
but when i scroll up and down, lazy loading..
it's my code following.
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
UICollectionViewCell *cell;
cell = [collectionView dequeueReusableCellWithReuseIdentifier:#"FaceImageCell" forIndexPath:indexPath];
UIImageView *fImageView = (UIImageView*)[cell viewWithTag:1];
[faceImageView setImage:[UIImage imageNamed:#"ic_loading"]];
NSLog(#"Loaded Image row : %d", indexPath.row);
ALAsset *asset;
asset = [_imageList objectAtIndex:indexPath.row];
dispatch_async(all_f_d_queue, ^{
ALAssetRepresentation *representation = [asset defaultRepresentation];
NSString *filename = [representation filename];
NSLog(#"%#", filename);
UIImage *image, *fullImage;
if ((fullImage = [_fullImageCache objectForKey:filename]) == nil) {
image = [UIImage imageWithCGImage:[asset thumbnail]
scale:[representation scale]
orientation:(UIImageOrientation)[representation orientation]];
vector<cv::Rect> f = [ImageUtils findFeature:image minsize:MIN_FACE_SIZE
withCascade:face_cascade];
Mat imageMat = [ImageUtils cvMatFromUIImage:image];
fullImage = [ImageUtils UIImageFromCVMat:imageMat];
[_fullImageCache setObject:fullImage forKey:filename];
}
dispatch_async(dispatch_get_main_queue(), ^{
[fImageView setImage:fullImage];
[cell setNeedsDisplay];
});
});
return cell;
}
Due to the many queue, it occured lazy loading.
I want to stop or cancel dispatch queue when loading cell is invisible.
How should I do for detecting cell to be invisible and for stopping dispatch queue??
Let me know please.
I could use asynctask in android. but I don't know how to implement that in iOS.
you can use NSOperation and NSOperationQueue.
It's pretty much the same as dispatchlib but is Object Oriented.
You can wrap the work on on NSOperation and then add that to an NSOperationQueue.
And then you can cancel an NSOperation whenever you want.
Probably have to read Apple documentations for better explanations of these two classes.
Take a look on this tutorial. It shows how you can download data asynchronously using NSOperationQueue and cancel this queue if user did scroll and this cell is not visible anymore.
im making a tableview loaded with some NSArrays, the cell contains two labels and a background image view loaded with a URL image. The problem is that the scrolling of the tableview is slow, its like freeze or something.... i think that is because of the Imageview but what can i do???
heres some of my code so you can see:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
// Return the number of rows in the section.
return _Title.count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
MyCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
int row = [indexPath row];
cell.TitleLabel.text = _Title[row];
cell.DescriptionLabel.text = _content[row];
cell.CellImageView.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:_ImageUrl[row]]]];
cell.CellImageView.contentMode = UIViewContentModeTop;
cell.CellImageView.contentMode = UIContentSizeCategoryMedium;
cell.backgroundColor = [UIColor clearColor];
return cell;
}
For extra info this tableview presents a Detailviewcontroller with this same info.
Load the image asynchronously(load it as the images come in). Look into great classes such as SDWebImage https://github.com/rs/SDWebImage
You are blocking the main thread with the following line of code:
cell.CellImageView.image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:_ImageUrl[row]]]];
My suggestion would be to use AFNetworking and replace the code with the following:
[cell.cellImageView setImageWithURL:[NSURL URLWithString:#"http://example.com/image.png"]];
Also, your pointers should start with a lower case letter. For instance, CellImageView should be cellImageView.
You are calling [NSData dataWithContentsOfURL:[NSURL URLWithString:_ImageUrl[row]]] from cellForRowAtIndexPath , which is not good idea. Because it'll go to the server every time a UITableViewCell is loaded.
You must use Asynchronous image loading & cache. These libraries might help you : (My personal favourite is SDWebImage)
1) https://github.com/rs/SDWebImage
2) https://github.com/nicklockwood/AsyncImageView
And more, you can refer to the sample code by Apple about LazyTableImages - https://developer.apple.com/library/ios/samplecode/LazyTableImages/Introduction/Intro.html
UPDATE:
For SDWebImage follow this guide. It's very good.
http://iosmadesimple.blogspot.in/2013/04/lazy-image-loading.html
To expand on #vborra's answer - the reason why your tableview is slow is that (in your code) the entire image MUST have finished downloading before it displays.
This is because dataWithContentsOfURL: is a synchronous method. You need to use asynchronous APIs to download images in the background and when they've finished downloading, display them on the screen.
Here is a code snippet from the github page adapted for your example. Make sure you add the folder SDWebImage and it's contents from https://github.com/rs/SDWebImage to your project.
#import <SDWebImage/UIImageView+WebCache.h>
...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
MyCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
int row = [indexPath row];
cell.TitleLabel.text = _Title[row];
cell.DescriptionLabel.text = _content[row];
[cell.CellImageView setImageWithURL:[NSURL URLWithString:_ImageUrl[row]]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
cell.CellImageView.contentMode = UIViewContentModeTop;
cell.CellImageView.contentMode = UIContentSizeCategoryMedium;
cell.backgroundColor = [UIColor clearColor];
return cell;
}
Note, if you're downloading images of all different sizes, you may end up with resizing issues which will also decrease your performance. The SDWebImage page has a link to an article to help you with this problem if you come across it. http://www.wrichards.com/blog/2011/11/sdwebimage-fixed-width-cell-images/
You may also experience a performance bump when using transparency and tableviews, but it depends on the rest of your code.
AFNetworking is an excellent tool to use, but might be overkill if you're not using networking anywhere else in your app.
load your image like this
//get a dispatch queue
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
//this will start the image loading in bg
dispatch_async(concurrentQueue, ^{
NSData *imageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:mapUrlStr]];
//this will set the image when loading is finished
dispatch_async(dispatch_get_main_queue(), ^{
cell.CellImageView.image = [UIImage imageWithData:imageData]
});
});