Scrolling breaks in uitableview - ios

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. */
});
});

Related

laggy scrolling tableview with images

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!

Fetching data from url and displaying it in a tableview

In my app i have to fetch images from a json webservice and display them in a table view. While scrolling downwards all the images come correct order but while i revert backwards all the images get over each other. I am using [ tableview reloaddata] then too its happening.
This happened to me in my app, where I have a leaderboards table. It is definitely because the tables are being reused.
The easiest way to fix this is simply setting the cell's image to nil first, then downloading and displaying the new image.
There are definitely other ways to do this, but I can definitely vouch for this one, as I use it in my own app, and it works great!!
Here's how I solve the problem.
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString * cellIdentifier = #"Cell";
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (!cell)
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
// Make sure you set the image for the cell to nil first
[cell.imageView setImage:nil];
// Then load your new image using the url and display it
dispatch_queue_t backgroundQueue = dispatch_queue_create("com.example.imagedownloadqueue", NULL);
dispatch_async(backgroundQueue, ^(void){
NSUrl * url = [NSUrl UrlWithString:#"www.example.com"]; // Url of image to download
UIImage * image = [UIImage imageWithData:[NSData dataWithContentsOfUrl:url]];
if (image)
{
dispatch_async(dispatch_get_main_queue(), ^(void) {
[cell.imageView setImage:image]; // Display image after it is downloaded
});
}
});
return cell;
}

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.

Asynchronous image loading in UITableViewCell memory issue

In my iPhone application I have a tableview with custom imageview and loading images from remote location using AsyncImageView class. It works nicely, but one issue is, if I scroll the table, cells will be dequeued and it again trying to get the images from server. So, the method for loading image from AsyncImageView class is calling again and again hence increases the memory allocation, and eventually the app crashes.
Here is my code:
- (UITableViewCell *) getCellContentView:(NSString *)cellIdentifier {
CGRect CellFrame = CGRectMake(0, 0, 300, 40);
CGRect userImageFrame = CGRectMake(5, 7, 36, 36);
UIImageView *userImage;
UITableViewCell *cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier] autorelease];
[cell setFrame:CellFrame];
userImage = [[UIImageView alloc]init];
userImage.frame = userImageFrame;
userImage.tag = 3;
[cell.contentView addSubview:userImage];
[userImage release];
return cell;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil)
cell = [self getCellContentView:CellIdentifier];
else
[[AsyncImageLoader sharedLoader] cancelLoadingImagesForTarget:cell.imageView];
UIImageView *userImage = (UIImageView *)[cell viewWithTag:3];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.selectionStyle = UITableViewCellSelectionStyleGray;
NSString *url = [[NSString alloc]initWithFormat:#"%#%#", CommonImageURL,[AllUsersProfileImageArray objectAtIndex:indexPath.row]];
NSURL *imageUrl = [NSURL URLWithString:[url stringByAppendingFormat:#"?%i", rand()]];
[url release];
userImage.image = [UIImage imageNamed:#"defaultPerson.png"];
userImage.imageURL = imageUrl;
return cell;
}
Is there any possible way to fix the issue? Please help.
The best solution will be caching the image that is already downloaded and displaying it from there.
You need to write code for that, or there are some libraries which provide such feature:
HJCache
SDWebImage
The popular AFNetworking library also includes a UIImageView category to load images from the web which is often overlooked. I found it to be quite efficient with respect to memory usage and easy to use.
http://afnetworking.github.com/AFNetworking/Categories/UIImageView+AFNetworking.html
I came across same trouble of memory leaks loading multiple images from server. My application needed fresh images response every time (functionality factor)
I was using NSURLConnection using asynch requests. I tried with
NSURLCache *sharedCache = [[NSURLCache alloc] initWithMemoryCapacity:0 diskCapacity:0 diskPath:0];
[NSURLCache setSharedURLCache:sharedCache]; // in DidFinishLoading & didFailWithError
receivedData=nil; // at didReceiveResponse
receivedData=[[NSMutableData alloc] init];
but nothing really helped , until I removed my cell
[cell removeFromSuperview]; in cellForRowAtIndexPath and initialized it again FOR every new NSURL request (additional if condition on cellForRowAtIndexPath but REALLY that payed off).
Probably the UIImageViews were never removed and new images n data were added constantly as fetched by responses. Removing old UITableViewCell for a new NSURLRequest worked in my case. Hope this helps someone like me lost in cleaning NSURLCache and still memory beefing up.
I posted a custom solution here
Download image asynchronously .
I think it works ok and requires very little code.

Resources