How to fix a slow scrolling table view - objective-c

I have a table view that's scrolling slowly. Does anyone know why that might be?
There is an image for each row, but even after the images are loaded it still stutters and scrolls slowly.
thanks for any help
here's my code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = #"SimpleTableCell";
SimpleTableCell *cell = (SimpleTableCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"SimpleTableCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
// Get item from tableData
NSDictionary *item = (NSDictionary *)[displayItems objectAtIndex:indexPath.row];
// display the youdeal deal image
photoString = [item objectForKey:#"image"];
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:photoString]]];
cell.titleLabel.text = [item objectForKey:#"supercat"];
cell.descriptionLabel.text = [item objectForKey:#"title"];
NSString *convertedLeftCount = [NSString stringWithFormat:#"%#",[item objectForKey:#"left_count"]];
cell.amountLabel.text = convertedLeftCount;
cell.thumbnailImageView.image = image;
cell.priceLabel.text = [item objectForKey:#"cat"];
return cell;
}

It's due to the image loading mechanism you used.
You are loading the image from url in the main thread. That's why the UI is blocked for some time also the dataWithContentsOfURL: is a synchronous call. So the UI will respond after getting the image data.
Apple states that the time taking processes like webrequest,parsing huge data etc must be done on other threads rather than main thread.
And all UI related tasks must be done on main thread.
Solutions:
Request the image in background thread, not in main thread.
Cache the image once you get it
Source code and Third Party Libraries
Here are some links which will help you to understand the basic idea of loaing image using asynchronous methods
LazyTableImages
HJCache
SDWebImage

The images are getting loaded every time a cell is loaded, because the imageWithData: doesn't use any cache.
Edit: I saw a comment that suggests loading images asynchronously. You already have your custom class for each cell so it should be easy to do it. If it were an answer I'd vote it up

I think You are trying to say this.
NSURL* url = [NSURL URLWithString:#"http://www.YourImageUrl.com"];
NSURLRequest* request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse * response,
NSData * data,
NSError * error) {
if (!error){
UIImage* image = [[UIImage alloc] initWithData:data];
// Now workout with the image
}
}];
This will make the asynchronous call, and load the images after tableview loaded i.e when the image load complete the image will show but the table will be loaded when the table view is needed to load.

Related

Images from url in UITableViewCell loads only when i scroll the table view

Images downloaded from url displays image in UITableViewCell only when i start scrolling the UITableView. Now i am using following code to load image from url:
NSURL* url = [NSURL URLWithString:imageURL];
NSURLRequest* request = [NSURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse * response,
NSData * data,
NSError * error) {
if (!error){
NSImage* image = [[NSImage alloc] initWithData:data];
// do whatever you want with image
}
}];
Thanks in advance.
You shouldn't be getting your images in - (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath you should create a utility class that acts as backing datastore. As you code stands now you could conceivable be re-requesting the image multiple times as the cell is scrolled into and out of view.
I Strongly recommend SDWebImage, which is very simple and powerful.
You can use setImageWithURL category , simply set image for imageView. For example
[cell.imageView setImageWithURL:[NSURL URLWithString:#"http://www.domain.com/path/to/image.jpg"]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];`
And you can simply save your image cache by [[SDImageCache sharedImageCache] storeImage:myImage forKey:myImageCacheKey] and simply query your image cache by [[SDImageCache sharedImageCache] queryDiskCacheForKey:myImageCacheKey done:^doneBlock];
Very simple and powerful, for more details checkout the here SDWebImage;

UITableView still slow with blocks

so i have a tableView in my app, which basically run the names and images of some user's friends from Facebook.
The problem is that when i drag the tableView up and down, it's slow and not smooth like it should be, although i use block to upload images from Facebook- so it's asynchrony.
Now, after i implement a block for upload images from Facebook, it is run faster then it was before the block- but still not smooth enough.
is anybody know how to deal with it?
here is some of my code:
cellForRowAtIndexPath:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:#"friendCell"];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:#"friendCell"];
[friendsTableView reloadData];
}
//set data for user's friends images
NSURL *url;
NSString *stringID;
stringID = [[NSString alloc]initWithFormat:#"https://graph.facebook.com/%#/picture?type=square",facebookFriendsID[indexPath.row] ];
url = [NSURL URLWithString:stringID];
//Calling block function to hendle user's friends images from facebook
[self downloadImageWithURL:url :stringID :cell completionBlock:^(BOOL succeeded, UIImage *image) {
if (succeeded) {
cell.imageView.image = image;
}
}];
//For friends name...
cell.textLabel.text = facebookFriendsName[indexPath.row];
cell.textLabel.font = [UIFont fontWithName:#"Noteworthy" size:17.0f];
[cell setSelectionStyle:UITableViewCellSelectionStyleNone];
cell.imageView.layer.masksToBounds = YES;
cell.imageView.layer.cornerRadius = 25.0;
return cell;
}
And the downloadImage block:
//For upload the images from Facebook asynchrony- using block.
- (void)downloadImageWithURL:(NSURL *)url : (NSString *)string : (UITableViewCell *)cell completionBlock:(void (^)(BOOL succeeded, UIImage *image))completionBlock
{
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
[NSURLConnection sendAsynchronousRequest:request
queue:[NSOperationQueue mainQueue]
completionHandler:^(NSURLResponse *response, NSData *data, NSError *error) {
if ( !error )
{
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
completionBlock(YES,image);
} else{
completionBlock(NO,nil);
}
}];
}
Thanks! :)
Your code has 4 problems, all of which are contributing to the slowness:
Delete [friendsTableView reloadData]; - you should never reload the table from inside cellForRowAtIndexPath:.
You should not reference cell from inside your completion block:
[self downloadImageWithURL:url :stringID :cell completionBlock:^(BOOL succeeded, UIImage *image) {
if (succeeded) {
cell.imageView.image = image;
}
}];
By the time your image downloads, cell may have been reused and be displaying different content. This can cause the wrong avatar image to appear. Instead, use indexPath to get the current cell by calling UITableViewCell *currentCell = [tableView cellForRowAtIndexPath:indexPath]; and set the image on that.
You are creating a new network request every time a cell appears. If the user scrolls very fast, you will have lots of simultaneous network requests. You may want to start network requests in scrollViewDidScroll: instead, when the scroll view slows down.
You are not de-duplicating network requests. If a user scrolls up and down really fast, they will generate lots of network requests to the same URL. You need to cache already-downloaded images and use those if the network request has already been made.*
The SDWebImage library has already solved all of these problems; you may simply want to use it instead of reinventing the wheel. At the very least, it's worth reading their code: https://github.com/rs/SDWebImage
* - NSURLCache may be caching the images for you, depending on the server's cache-control headers. But even so, this cache is slow (it caches the NSData representation, not the UIImage representation), and NSURLConnection will not stop you from starting 4 identical requests simultaneously.
Also, pay attention, that you load your image data for second time from main thread in request completion block:
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
you need to use NSData *data parameter from completionHandler - ^(NSURLResponse *response, NSData *data, NSError *error)
UIImage *image = [UIImage imageWithData:data];
BUT, as Aaron Brager noted, you don't need reinventing the wheel instead of using ready solution, like SDWebImage. so, all it matter if only you going to clarify how it works

UITableView: How to load an image only once then use it in all cells

I'm creating an app using the Twitter API and parsing with JSON and every time I load the image into the cells it's taking multiple images and everything runs slowly. How would I go on by getting the image once then put the same image into all cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"TweetCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
NSDictionary *tweet = [tweets objectAtIndex:indexPath.row];
NSString *text = [tweet objectForKey:#"text"];
NSString *time = [tweet objectForKey:#"created_at"];
time = [time stringByReplacingOccurrencesOfString:#" +0000 "withString:#"/"];
NSString *twitterImage = [[tweet objectForKey:#"user"] objectForKey:#"profile_image_url_https"];
NSString *completeImage = [NSString stringWithFormat:#"%#", twitterImage];
NSData * imageData = [[NSData alloc] initWithContentsOfURL: [NSURL URLWithString: completeImage]];
imageLabel.image = [UIImage imageWithData: imageData];
cell.imageView.image = [UIImage imageWithData: imageData];
cell.textLabel.text = text;
cell.textLabel.numberOfLines = 3;
cell.detailTextLabel.text = [NSString stringWithFormat:#"%#", time];
}
return cell;
}
Looks like this right now but really laggy when I scroll.
http://gyazo.com/8ab8325f3921fdb7e4f0ea0107d389ac.png
Looks to me like the problem is in these lines:
NSDictionary *tweet = [tweets objectAtIndex:indexPath.row];
NSString *twitterImage = [[tweet objectForKey:#"user"] objectForKey:#"profile_image_url_https"];
I believe that is getting a new copy of the image for each cell. Each indexPath.row is a new tweet, thus you are getting multiple twitterImage
You should use cache for images. I hope this link helps you:
http://khanlou.com/2012/08/asynchronous-downloaded-images-with-caching/
If it's always the same picture, load it before the table's loading in a class parameter (for example in the viewDidLoad) and always use this parameter.
If it's dynamic, load the image in the background using performSelectorInBackground.
The problem that you are facing is because you are downloading all the images on the main thread.
To solve this either :
Download images using a separate thread using NSOperationQueue.
A good tutorial on the same : http://www.raywenderlich.com/19788/how-to-use-nsoperations-and-nsoperationqueues
Use this class 'AsyncImageView'. I have used this and it works fine. So, instead of UIImageView you will need to use the class AsyncImageView and this library will manage the downloading for you asynchronously.
https://github.com/nicklockwood/AsyncImageView

Loading images from a URL into a UITableViewCell's UIImageView

I have the following code: (Note: newsImageURL is an NSArray)
NSString *imagesURL = #"http://aud.edu/images/newsimage01.png,http://aud.edu/images/newsimage04.png,http://aud.edu/images/newsimage02.png,http://aud.edu/images/newsimage03.png,http://aud.edu/images/newsimage01.png,http://aud.edu/images/newsimage04.png,http://aud.edu/images/newsimage01.png,http://aud.edu/images/newsimage04.png,http://aud.edu/images/newsimage01.png,http://aud.edu/images/newsimage04.png,";
newsImageURL = [[NSArray alloc] initWithArray:[AllNewsHeadLine componentsSeparatedByString:#","]];
I am trying to load these images into a cell using the code below:
NSData* imageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString: [newsImageURL objectAtIndex:indexPath.row]]];
cell.image = [UIImage imageWithData:imageData];
The image loads fine when I use this line instead:
cell.image = [UIImage imageNamed:#"imagename.png"];
What am I doing wrong?
You should use an existing framework which supports caching, default place holders and lazy loading of images.
https://github.com/rs/SDWebImage is a good and simple framework
#import "UIImageView+WebCache.h"
...
- (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://aud.edu/images/newsimage01.png,http://aud.edu/images/newsimage04.png"]
placeholderImage:[UIImage imageNamed:#"placeholder.png"]];
cell.textLabel.text = #"My Text";
return cell;
}
You can load data this way:
NSData *data = [NSData dataWithContentsOfURL: [NSURL URLWithString: [newsImageURL objectAtIndex:indexPath.row]]];
And you can instantiate the array of URLs this way too:
NSArray *newsImageURL = [imagesURL componentsSeparatedByString:#","];
However, if someone scrolls around on the table a great deal, you may end up loading the images many times over as the cells are recycled.
Maybe you can have a try https://github.com/SpringOx/ALImageView.git.It is much simpler than SDWebImage.You only need two source files(ALImageView.h/ALImageView.m).You can reuse the image view to reload different urls in a tableview cell.
Support local and memory cache;
Support place holders;
Support tap touch(target-action);
Support corner for the image view;
You Can Easily load all the images with the Help of Following code ,Details Array is a Main Array
Details Array :- {
"item_code" = 709;
"item_desc" = Qweqweqwe;
"item_name" = AQA;
"item_photo" = "http://toshaya.com/webapp/snap&sell/api/img_items/709.png";
"item_price" = "0.00";
"item_till" = "20-25";
"item_type" = Orange;
latitude = "";
longitude = "";
}
With the Help of Following Code Retrieve The Photo-URL into String
NSString * result = [[DetailArray objectAtIndex:indexPath.row]objectForKey:#"item_photo"]; //componentsJoinedByString:#""];
NSLog(#"%#",result);
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:result]];
cell.icon.image = [UIImage imageWithData:imageData];

Images from URLs (RSS Reader) displaying in table view - problem

I'm having a problem with a table view and some custom cells. Here is my code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSLog(#"cellForRowAtIndexPath");
static NSString *MyIdentifier = #"customCell";
EventTableCell *cell = (EventTableCell *)[tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
//cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:MyIdentifier] autorelease];
[[NSBundle mainBundle] loadNibNamed:#"EventTableCell" owner:self options:nil];
cell = self.eventCell;
}
//Set up the cell
int storyIndex = [indexPath indexAtPosition: [indexPath length] - 1];
[[cell eventNameLabel] setText:[[stories objectAtIndex: storyIndex] objectForKey: #"title"]];
[[cell eventDateLabel] setText:[[stories objectAtIndex: storyIndex] objectForKey: #"date"]];
NSString * storyLink = [[stories objectAtIndex: storyIndex] objectForKey: #"link"];
// clean up the link - get rid of spaces, returns, and tabs...
storyLink = [storyLink stringByReplacingOccurrencesOfString:#" " withString:#""];
storyLink = [storyLink stringByReplacingOccurrencesOfString:#"\n" withString:#""];
storyLink = [storyLink stringByReplacingOccurrencesOfString:#" " withString:#""];
//NSString *string = [[NSString alloc] stringWithString:[[stories objectAtIndex: storyIndex] objectForKey: #"link"]];
NSURL *url = [NSURL URLWithString:storyLink];
NSData *data = [[NSData alloc] initWithContentsOfURL: url];
UIImage *image = [UIImage imageWithData: data];
[[cell eventImage] setImage:image];
return cell;
}
My table has a lot of these custom cells in it, more than the view can show at once. So therefore I need to scroll up/down to see all the cells. My problem is this, when I scroll it lags a lot and in the debugger console it calls cellForRowAtIndexPath as each new cell comes into the view. Each cell holds an image which is downloaded from a URL and hence has to be converted to a UIImage. I believe this is whats causing the lag.
Can anyone direct me as to what I need to do in order to download the images and have them display in the cells without causing major lag in the app?
IE: Where would I put the download/conversion code so that the image is already stored as an image before the cell needs to be dispayed?
Thanks,
Jack
If you want to pre-download the images, you could do it at any point in your application and cache them for later. For example, you could start the downloads just after you download and parse the RSS feed. You can save them in the directory returned by [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0]. It would be best to use asynchronous method to download them, and have just a few downloads active at a time for best performance.
If you want to load them as needed, set a placeholder image when you allocate the cell and use an asynchronous NSURLConnection or the like to do the download. Once the download completes, replace the placeholder with the real image (and cache it for reuse later, of course).