laggy scrolling tableview with images - ios

I added images to my tableView cells and it made it laggy. I am still new to objective c and I do not understand what is causing this or how to fix it. Any help is greatly appreciated!
group[PF_GROUP_LOGO] is simply a string in my database that is unique to each object. The code works, it is just really laggy when trying to scroll.
//-------------------------------------------------------------------------------------------------------------------------------------------------
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
//-------------------------------------------------------------------------------------------------------------------------------------------------
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell"];
if (cell == nil) cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"cell"];
PFObject *group = groups[indexPath.row];
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:group[PF_GROUP_LOGO]]]];
cell.imageView.image = image;
cell.detailTextLabel.text = [NSString stringWithFormat:#"%d users", (int) [group[PF_GROUP_MEMBERS] count]];
cell.detailTextLabel.textColor = [UIColor lightGrayColor];
return cell;
}

There are so many tools out there that can help you with this.
At a base level, the issue is that you are running a long process on the main thread, which blocks the UI. Loading an image from a non-local URL is time consuming, and you should do this on a background thread, so the UI is not blocked.
Again, there are so many ways to do this, and I strongly suggest you do some research on async resource loading, but this is one thing you can do within the confines of your own example:
//-------------------------------------------------------------------------------------------------------------------------------------------------
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
//-------------------------------------------------------------------------------------------------------------------------------------------------
{
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"cell"];
if (cell == nil) cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:#"cell"];
PFObject *group = groups[indexPath.row];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{ // go to a background thread to load the image and not interfere with the UI
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:group[PF_GROUP_LOGO]]]];
dispatch_async(dispatch_get_main_queue(), ^{ // synchronize back to the main thread to update the UI with your loaded image
cell.imageView.image = image;
});
});
cell.detailTextLabel.text = [NSString stringWithFormat:#"%d users", (int) [group[PF_GROUP_MEMBERS] count]];
cell.detailTextLabel.textColor = [UIColor lightGrayColor];
return cell;
}
I would also recommend using AFNetworking as the author has built a very good category on top of UIImageView that allows you to load an image from a web URL in the background automatically. Again, there are many schools of thought on this process, and this is just one idea. I would recommend reading this for a full on tutorial on the topic.
I hope this is helpful!

Related

iOS: UITableView cells gets mixed up while scrolling

I am facing one issue regarding UITableView cells get mixed up while scrolling, specially for images.
I am not sure why this is going one.I have change the patter for displaying then also its get mixed up. Below is my code.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"FriendsCell";
FriendsTableCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil)
{
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:#"FriendsTableCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}
return cell;
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
FriendsTableCell *simpleTableCell = (FriendsTableCell *)cell;
_model = [_contentArray objectAtIndex:indexPath.row];
simpleTableCell.nameLabel.text = _model.fullname;
if(_friendsSegmentControl.selectedSegmentIndex == 0) {
simpleTableCell.followButton.hidden = NO;
simpleTableCell.removeButton.hidden = NO;
simpleTableCell.unBlockButton.hidden = YES;
simpleTableCell.ignoreRequest_btn.hidden = YES;
simpleTableCell.rejectRequest_btn.hidden = YES;
simpleTableCell.acceptRequest_btn.hidden = YES;
simpleTableCell.removeButton.tag = indexPath.row;
[simpleTableCell.removeButton addTarget:self action:#selector(deleteFriend_btn:) forControlEvents:UIControlEventTouchUpInside];
simpleTableCell.followButton.tag = indexPath.row;
if([_model.isfollowed isEqualToString:#"YES"]) {
[simpleTableCell.followButton setImage:[UIImage imageNamed:#"follow_yellow.png"] forState:UIControlStateNormal];
[simpleTableCell.followButton addTarget:self action:#selector(unfollowFriend_btn:) forControlEvents:UIControlEventTouchUpInside];
}
else {
[simpleTableCell.followButton setImage:[UIImage imageNamed:#"follow_white.png"] forState:UIControlStateNormal];
[simpleTableCell.followButton addTarget:self action:#selector(followFriend_btn:) forControlEvents:UIControlEventTouchUpInside];
}
}
NSString *escapedString = (NSString *)CFBridgingRelease(CFURLCreateStringByAddingPercentEscapes(NULL,(__bridge CFStringRef) _model.prof_pic,NULL,(CFStringRef)#"!*'();:#&=+$,/?%#[]",kCFStringEncodingUTF8));
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^(void) {
NSData *imageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:#"http://www.touristtube.com/%#",escapedString]]];
UIImage *image = [UIImage imageWithData:imageData];
dispatch_async(dispatch_get_main_queue(), ^{
simpleTableCell.profileImageView.image = image;
});
});
}
Two things you need to ensure while creating cells for table view-
1) Regarding reusable cells- As you are using reusable cells, so in case if it's dequeued it will show the data for the index path for which it was originally created.
It's right that you are updating each cell(whether newly created/ dequeued) with the data for the corresponding index path just before displaying it in
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
Also you can update the data for each cell' container for corresponding index path in
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
as well.
So, please ensure if the cell is reused you should update the data of each container(cell's subviews) with the data for the current index path. Doing so will resolve your issue of cell's content getting overlapped.
2) Regarding asynchronously downloading the images for cells- Also as you are downloading the images asynchronously, so it will not block the main thread and as soon as the image is downloaded you need to switch to main thread to set the image to the cell.
Now the thing to remember in case of asynchronous image downloading is that you should always access the cell for the correct index path on main thread and set the downloaded image to the cell's image view for the index path for which it was scheduled to be downloaded.
So for the images you should update your image downloading method as
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0ul);
dispatch_async(queue, ^(void) {
NSData *imageData = [[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:#"http://www.touristtube.com/%#",escapedString]]];
UIImage *image = [UIImage imageWithData:imageData];
dispatch_async(dispatch_get_main_queue(), ^{
FriendsTableCell *cell = [tableView cellForRowAtIndexPath:indexPath];
cell.profileImageView.image = image;
[cell setNeedsLayout];
});
});
this will resolve your issue of cell's image intended to be displayed for a index path but actually showing on a cell at some other index path.

Why my tableview is slow?

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]
});
});

Scrolling breaks in uitableview

I am having a problem viewing my tableview when i get the data of my cells from a server. If i do not use photos there is no breaks in the scrolling, but i want to use the images also. Can anyone knows how can i resolve this? I am getting the data from a plist in my server.
Here is the code of the image that makes the scrolling breaks (i am using custom style cell)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier forIndexPath:indexPath];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
NSURL *URL = [[NSURL alloc] initWithString:[[self.content objectAtIndex:indexPath.row] valueForKey:#"imageName"]];
NSData *URLData = [[NSData alloc] initWithContentsOfURL:URL];
UIImage *img = [[UIImage alloc] initWithData:URLData];
UIImageView *imgView = (UIImageView *)[cell viewWithTag:100];
imgView.image = img;
....
If you mean that the scrolling stops and starts, this might be because if the images are loaded from a server (which might take a noticeable amount of time to do), executing on the main thread causes freezing.
If this is the case, the fix is to fetch the images in another thread. Fortunately iOS has a fairly easy to use multithreading system called Grand Central Dispatch.
Here's some example code:
dispatch_queue_t q = dispatch_queue_create("FetchImage", NULL);
dispatch_async(q, ^{
/* Fetch the image from the server... */
dispatch_async(dispatch_get_main_queue(), ^{
/* This is the main thread again, where we set the tableView's image to
be what we just fetched. */
});
});

UITableView Custom cells, built of UIViews, can be concurrent?

I am building my cellViews like this:
- (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString* cellIdentifier=#"cell";
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if(cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease];
}
UIImageView cellView = [[UIImageView alloc] initWithFrame:rectCellFrame];
NSError* error=nil;
NSData* imageData = [NSData dataWithContentsOfURL:imageArray[indexPath.row] options:NSDataReadingUncached error:&error];
UIImage* theImage= [UIImage ImageWithData:imageData];
[cellView setImage:theImage];
[cell addSubView:cellView];
.
.
.
.
[cell addSubView:moreViews];
}
Since the loading time (even when the images are cached) is very slow, I need to make this concurrent. But I would like to still be using my code with UIViews/UIImageViews.
Is there a way for me to show a placeholder and when relevant, ie cellView is finished building from all subviews, update the image instead of the placeholder?
Sure. You can set up all the heavy slow code in a asynchronous task. It's often down when images need to be downloaded. I'm sure it's covered in at least 1 of the WWDC videos on Table Views, but I've no clue which one or how old it would be by now.
- (UITableViewCell *)cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// place holder image for the moment
[cellView setImage:placeHolderImage];
// run code to get the real image in asynchronous task
dispatch_async(self.contextQueue, ^{
UIImage *realImage = [thingy imageFromTimeConsumingTask];
// update cell on main thread (you need to do all UI stuff on main thread)
dispatch_async(dispatch_get_main_queue(), ^{
[cellView setImage:realImage];
});
});
}

GCD jumbles the data on scrolling tableview

I'm using grand central dispatcher to load images from server but when i scroll the table the data, i.e. images, jumbles - means 1st image comes to some other place and like wise other images do.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"ItemImageCellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] ;
cell.selectionStyle=UITableViewCellSelectionStyleNone;
}
NSDictionary *item=[responseDictionary objectAtIndex:[indexPath row]];
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul);
NSString *actionForUser=[item objectForKey:#"action"];
objc_setAssociatedObject(cell,
kIndexPathAssociationKey,
indexPath,
OBJC_ASSOCIATION_RETAIN);
dispatch_async(queue, ^{
if([actionForUser isEqualToString:like])
{
NSURL *url = [NSURL URLWithString:[item objectForKey:#"user_image"]];
NSData *data1 = [[NSData alloc] initWithContentsOfURL:url];
UIImage *image1 = [[UIImage alloc] initWithData:data1];
//userProfileimage
UIButton *userImageButton = [[UIButton alloc] initWithFrame:CGRectMake(10,5, 40,40)];
userImageButton.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
userImageButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
[userImageButton setBackgroundImage:image1 forState:UIControlStateNormal];
[userImageButton addTarget:self
action:#selector(userImageButtonclick:)
forControlEvents:UIControlEventTouchDown];
[cell.contentView addSubview:userImageButton];
}
});
return cell;
}
This is because by the time your async method has finished, cell has been recycled and used for a different index path, so you're updating the wrong cell.
At the point of update, get the cell reference by using the tableview's (not the data source method) cellForRowAtIndexPath: method. This will return the correct cell, or nil if the cell isn't on the screen any more. You can update this cell safely.
You should probably be adding the image data to your model as well so you aren't downloading it repeatedly.
As an example, instead of this line:
[cell.contentView addSubview:userImageButton];
You should have something like this:
UITableViewCell *cellToUpdate = [tableView cellForRowAtIndexPath:indexPath];
[cellToUpdate.contentView addSubview:userImageButton];
There are further problems with your code; you are not caching the images, you will be adding this subview every time this cell comes on screen, and if the cell is reused for a case where it doesn't need the button, the button will still be present. I have only addressed the "GCD jumbling" as described in your question.

Resources