I have performance issue in UITableView scrolling when I use imageWithContentsOfFile instead of imageNamed. Following is my code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
UniversalAppAppDelegate *delegate = (UniversalAppAppDelegate *) [[UIApplication sharedApplication] delegate];
NSDictionary *res = [[delegate comments] objectAtIndex:indexPath.section];
if (cell == nil)
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease];
......
// This line has a problem...when I use imageNamed here instead of imageWithContentsOfFile, it works absolutely fine
imageView = [[UIImageView alloc] initWithImage:[UIImage imageWithContentsOfFile:imagepath]];
[imageView setFrame: CGRectMake(30, 10, 55, 55)];
[sectionHeaderView addSubview:imageView];
......
cell.backgroundView = sectionHeaderView;
return cell;
I have also tried caching image but still same issue. Only imageNamed works perfectly fine but I can't use it as I need to specify path for image stored in documents folder and changes dynamically. Could anyone please tell me how could I resolve this issue?
Caching code:
NSMutableDictionary *thumbnailCache = [[NSMutableDictionary alloc] init];
- (UIImage*)thumbnailImage:(NSString*)fileName
{
UIImage *thumbnail = [thumbnailCache objectForKey:fileName];
if (nil == thumbnail)
{
NSString *thumbnailFile = [self filePath:fileName];
thumbnail = [UIImage imageWithContentsOfFile:thumbnailFile];
[thumbnailCache setObject:thumbnail forKey:fileName];
}
return thumbnail;
}
Usage:
imageView = [[UIImageView alloc] initWithImage:[delegate thumbnailImage:[res objectForKey:#"ImageName"]]];
You could try building an array of UIImages in the viewDidLoad method of the controller, and use the images in that array in your cellForRowAtIndexPath.
This will get rid of the need to call imageWithContentsOfFile for each image when drawing the tableview.
You could for example build the array in a seperate thread, and show a loading indicator while this happens. It would mean a small loading time at first, but a smooth scrolling tableview.
Related
I'm loading asynchronously images into UITableView and each row has different image. the problem is when i scroll the UITableView the previous images are loaded again in the new rows and after a while these images are changed to the correct ones (this problem is gone when i removed dequeueReusableCellWithIdentifier but it reduces the performance)
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if(cell == nil) {
NSArray *nibObjects = [[NSBundle mainBundle] loadNibNamed:#"CustomCellFriends" owner:self options:nil];
cell = [nibObjects objectAtIndex:0];
nibObjects = nil;
}
UIImageView *avatar = (UIImageView *)[cell viewWithTag:100];
UILabel *name = (UILabel *)[cell viewWithTag:2];
UILabel *city = (UILabel *)[cell viewWithTag:3];
EntryProfile *entryForRow = [entriesProfileArray objectAtIndex:indexPath.row];
avatar.image = nil;
NSString *strLinkImage = [entryForRow.apiAvatarPath stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSArray *args = [NSArray arrayWithObjects:avatar,strLinkImage,[NSString stringWithFormat:#"%d",indexPath.row], nil];
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(addPhotoWithImageView:) object:args];
[operationQueue addOperation:operation];
[operation release];
// Set the name of the user
name.text = entryForRow.name;
return cell;
}
// Method
- (void)addPhotoWithImageView:(NSArray *)args
{
UIImageView *imageView = (UIImageView *)[args objectAtIndex:0];
NSString *strLinkImage = (NSString *)[args objectAtIndex:1];
NSString *index = (NSString *)[args objectAtIndex:2];
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[NSString stringWithFormat:#"http://www.example.com%#",strLinkImage]]];
UIImage *imageField = [[UIImage alloc] initWithData:imageData];
if (imageData) {
[imageView performSelectorOnMainThread:#selector(setImage:) withObject:imageField waitUntilDone:NO];
}
[imageField release];
}
If there are people still facing this issue, I see the answers above do not really help.
2 steps to take:
You need to "cancel" the asynchronous request that is ongoing for the image view and then set a new image asynchronously with a placeholder image till the async request goes through
Ideally, do not send your async request while the user is scrolling through
(tableview/scrollview is decelerating) - try to avoid making new requests and just set the placeholder in such cases (unless you have a way of confidently knowing that the request was done before and hence it is likely that the image is found in the local disk cache for the image url)
The above 2 steps will keep your functionality as intended and give you good performance too.
Try to implement your own UItableViewCell and set the all your cell properties to nil in the PrepareForReuse method.
I would also recomend you to take a look at this project in order to load asynchronously remote images:
https://github.com/nicklockwood/AsyncImageView
I used it with great success in different projects.
in cellForRowAtIndexPath,
before you call:
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self selector:#selector(addPhotoWithImageView:) object:args];
force yourCell imageView.image to nil
imageView.image = nil;
this way you clean the contents image of old cell, then it will be redraw with right image when it's loaded...
EDIT
maybe nil doesn't works...
try a placeHolder image, a real png file (with a color, for test, then it may be a transparent file...)
Had a similar issue the other day and the way I solved it was the following ( I didn't notice any performance reduction ).
UITableViewCell *cell;
if (cell == nil) {
cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
// rest of the code
}
So basically invoke the dequeue inside the if.
This is a classic problem, easily solved.
In your cellForRowAtIndexPath: implementation, you can include code like this:
if (!tableView.decellerating) {
// set avatar.image to its correct UIImage value how ever you like,
// in the background if necessary
} else {
// table is scrolling...
avatar.image = nil;
}
And then implement this method in your view controller:
-(void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
{
NSArray *visibleCells = [self.tableView visibleCells];
[visibleCells enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
UITableViewCell *cell = (UITableViewCell *)obj;
UIImageView *avatar = (UIImageView *)[cell viewWithTag:100];
avatar.image = ...; // Do what you need to do to set the image in the cell
}];
}
You will also need to add UIScrollViewDelegate to the list of protocols your view controller adopts.
The idea here is that as long as the table view is moving, don't bother loading the images. Your performance will skyrocket.
I'm using the xml for fetching the data and display it in the uitable. In the XML i have a separate tag for the image as "link". I'm displaying this image as the cell image. Everything workings fine when the applications loads, but when i scroll the table the images in the cell are getting changed.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
cell.accessoryType=UITableViewCellAccessoryNone;
cell.selectionStyle=UITableViewCellSelectionStyleNone;
//cell. separatorStyle=UITableViewCellSeparatorStyleNone;
titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(110, 10, 190, 130)];
[titleLabel setTag:2];
titleLabel.numberOfLines=7;
[titleLabel setFont:[UIFont systemFontOfSize:14]];
[titleLabel setTextColor:[UIColor blackColor]];
[titleLabel setBackgroundColor:[UIColor clearColor]];
[cell.contentView addSubview:titleLabel];
[titleLabel release];
imageView= [[UIImageView alloc] initWithFrame:CGRectMake(5, 20, 90, 110)];
[cell.contentView addSubview:imageView];
}
NSDictionary *appRecord = [responseArr objectAtIndex:indexPath.row];
NSString *urlLink;
urlLink=[NSString stringWithFormat:#"%#",[appRecord objectForKey:#"link"]];
NSLog(#"LINK : %#",urlLink);
if([urlLink length] ==0)
{
imageView.image=[UIImage imageNamed:#"ioffer.png"];
}
else {
NSData *imageData = [NSData dataWithContentsOfURL:[NSURL URLWithString:urlLink]];
UIImage *image = [[UIImage alloc] initWithData:imageData];
imageView.image = image;
}
UILabel *desclbl = (UILabel*)[cell.contentView viewWithTag:2];
desclbl.text = [NSString stringWithFormat:#"%#",[appRecord objectForKey:#"description"]];
return cell;
}
I have used these code in the table's cellForRowAtIndexPath. responseArr is the array where i stored the links.
Can any one help me in this issue.
Thanks in advance
Malathi
If you reuse an existing table view cell, i.e. the code skips the first if-block, then imageView isn't initialized (where is it declared anyway?). That means even though you set a new text, some random image is reused.
The fix is to make sure that imageView is intialized in all cases.
And please do declare it locally in this method. Everything else seems very dangerous to me.
The "implied else" of the if (cell == nil) is that it's reusing a cell from the table's cache. It doesn't reset anything about those, it just picks them up and uses them. Which means that any element you've got on there needs to be cleared manually, especially if it's going to be some time before they're filled in.
You're already doing some defaulting, here:
if([urlLink length] ==0)
{
imageView.image=[UIImage imageNamed:#"ioffer.png"];
}
If you just make that NOT conditional -- ie just say
imageView.image=[UIImage imageNamed:#"ioffer.png"];
what you'll get is, your cell will load with its default image, then when the image loads remotely it'll replace it.
NOW: You're doing that image load synchronously. Which means your whole UI is going to freeze while the web request is being made. Not great.
I had the same issue. The cells are reused at an other index, when scrolling.
Try putting the code, that fills the cells with data, outside the "if (cell == nil)". Worked for me.
Best regards,
Alexander
You also use the custom cell instead of this and check each time
as like given below example-
Condition-
if(shout.creator.avatarImage){
avatarView.image = shout.creator.avatarImage;
}
else{
FetchImageOperation *operation = [[FetchImageOperation alloc] init];
operation.delegate = self;
operation.urlString = shout.creator.avatarURL;
[operationQueue addOperation:operation];
[operation release];
}
-(void)fetchthumbImageOperation:(FetchThumbImageOperation *)operation didFetchImage:(UIImage *)image
{
avatarView.image= image;
}
Here, i check the condition for image in tableview data source.
-(UITableViewCell *)tableView:(UITableView *)tv cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
}
I'm semi new to developing for iOS.
And when i try to customize a tableview cell to put an image under the description text, it works, until i scroll, then it gets all messed up.
When i scroll down, then back up, the image is in a cell it shouldn't be in. :/
I deleted the code because it wasn't working. But what i was doing was creating a UIView with a frame, with a UIImageView inside it. Then i add it to cell.contentView.
Which works, until i scroll.
So could someone please help :/
Edit: here is what it looks like for anyone interested, i got it working. (Thanks Jonathan)
#define LEVEL_TAG 1
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UIImageView *showLevel;
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
showLevel = [[[UIImageView alloc] initWithFrame: CGRectMake(65, 50, 0, 11)] autorelease];
showLevel.tag = LEVEL_TAG;
[cell.contentView addSubview: showLevel];
} else {
showLevel = (UIImageView *)[cell.contentView viewWithTag:LEVEL_TAG];
}
NSDictionary *dictionary = [listOfItems objectAtIndex:indexPath.section];
NSArray *levelArray = [dictionary objectForKey:#"Levels"];
NSString *levelValue = [levelArray objectAtIndex:indexPath.row];
UIImage *levelImage = [UIImage imageWithContentsOfFile:[[NSBundle mainBundle] pathForResource: levelValue ofType:#"png"]];
CGImageRef starImage = levelImage.CGImage;
NSUInteger levelWidth = CGImageGetWidth(starImage);
showLevel.frame = CGRectMake(65, 50, levelWidth, 11);
showLevel.image = levelImage;
return cell;
}
It's because of the way the tableView reuses old cells, rather than creating new ones every time the user scrolls, which would make everything slower, and less efficient.
Have a look at Listing 5-3 on the Apple Documentation about customizing UITableViewCells, it shows you how to set up the cell, within the if statement where the cell is actually created, and not where the cell could have been reused.
hi i am working with Twitter, i got problem with images, i have tweet view have UITableview, it contains all tweet with user photo, if i load those photos in each cell when i scroll the UITableview, it scrolling very very slowly please suggest me to reduce the memory size of photos and scroll the UITableView fast.
i heard the thumbnail can reduce the memory size, does it.(if not which method i have to choose and what thumbnail method do's) if so how to do that in this code (table view cell code)
//Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if(!cell) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewStyleGrouped reuseIdentifier:nil] autorelease];
UIImageView *myImage = [[UIImageView alloc]initWithFrame:CGRectMake(6,10,58,60)] ;
NSURL *url = [NSURL URLWithString:[(Tweet*)[tweetArray objectAtIndex:indexPath.row] image_url]]; //here Tweet is other class and image_url is method in the Tweet class
NSData *data = [[NSData alloc] initWithContentsOfURL:url];
[myImage setImage: [UIImage imageWithData:data]];
[cell.contentView addSubview:myImage];
[myImage release];
}
return cell;
}
Thank you,
please suggest me
The reason for slow scroll of the table view is that you are trying to get the image data for every cell on the main thread. UI is getting blocked while it is going to fetch the image data from the URL and, According to me its not good to download images on main thread while table view is also loaded.
Instead of using this approach, You should have to use NSOperationQueue, NSOperation and NSThread for async load of images to the appropriate cell.
If you need more help or simple code like 2-3 function to download images async...
Here are the functions....
Where you are parsing/getting the values only call [self startLoading]; It will load images without blocking UI.
- (void) startLoading {
NSOperationQueue *queue = [[[NSOperationQueue alloc]init]autorelease];
NSInvocationOperation *op = [[NSInvocationOperation alloc]initWithTarget:self selector:#selector(loadImagesInBackground) object:nil];
[queue addOperation:op];
[op release];
}
-(void) loadImagesInBackground {
int index = 0;
for (NSString urlString in [(Tweet*)[tweetArray objectAtIndex:indexPath.row] image_url]) {
NSURL *url = [NSURL URLWithString:urlString];
NSData *data = [[NSData alloc] initWithContentsOfURL:url];
[myImageArray addObject: [UIImage imageWithData:data]];
index++;
if(index/3==0)
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:YES];
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if(!cell) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewStyleGrouped reuseIdentifier:nil] autorelease];
UIImageView *myImage = [[UIImageView alloc]initWithFrame:CGRectMake(6,10,58,60)] ;
[myImage setImage: [myImageArray objectAtIndex:indexpath.row]];
[cell.contentView addSubview:myImage];
[myImage release];
}
return cell;
}
This article (Download images for a table without threads) might have your answer.
There's also this sample in the iOS dev library, which shows how to load images assynchronously, as quickly as possible.
For sure you want to download your images lazily and asynchronously as described by other answerers.
You also probably want to resize it to your thumbnail size and store it in memory at the smaller size. See this article for tutorial and actual usable library code to do that (scroll down past the rant about copy-and-paste development).
EDIT: You clearly need more help with the background download part. Here's how I do it.
Install ASIHTTPRequest, which is a third party library that GREATLY simplifies HTTP client work. Follow the instructions to install it in your project, and make sure you put #include "ASIHTTPRequest.h" at the top of your .m file.
Then in your tableView: cellForRowAtIndexPath:, go:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *identifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if(!cell) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier] autorelease];
UIImageView *myImage = [[UIImageView alloc]initWithFrame:CGRectMake(6,10,58,60)] ;
[cell.contentView addSubview:myImage];
[myImage release];
}
// so at this point you've got a configured cell, either created fresh
// or dequeued from the table's cache.
// what you DON'T have is a pointer to the uiimageview object. So we need to
// go get it.
UIImageView *theImageView;
for (UIView *view in cell.contentView.subviews) {
if ([view isKindOfClass:[UIImageView class]) {
theImageView = view;
break;
}
}
NSURL *url = [NSURL URLWithString:[(Tweet*)[tweetArray objectAtIndex:indexPath.row] image_url]]; //here Tweet is other class and image_url is method in the Tweet class
ASIHTTPRequest *req = [ASIHTTPRequest requestWithURL:url];
[req setCompletionBlock:^{
NSData *data = [req responseData];
// This is the UIImageView we extracted above:
[theImageView setImage: [UIImage imageWithData:data]];
}];
// this will start the request in the background, and call the above block when done.
[req startAsynchronous];
}
// Then return the cell right now, for the UITableView to render.
// The image will get filled in later when it returns from the server.
return cell;
}
using sdk 4.1. I'm getting growing memory footprint followed by crash (observed in Instruments) when loading a thumbnail image into imageview in table view cell. In addition scrolling is very jerky even with just 7-8 cells
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *FavouritesCellIdentifier = #"cellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:cellIdentifier] autorelease];
UIImageView* imgView = [[UIImageView alloc] initWithFrame:CGRectMake(10,
16, 64, 64)];
imgView.tag = kImageLabelTag;
[cell.contentView addSubview:imgView];
[imgView release];
}
UIImageView* imgView = (UIImageView*)[cell viewWithTag:kImageLabelTag];
NSData *contactImageData = (NSData*)ABPersonCopyImageDataWithFormat(personRef,
kABPersonImageFormatThumbnail);
UIImage *img = [[UIImage alloc] initWithData:contactImageData];
[imgView setImage:img];
[contactImageData release];
[img release];
return cell;
}
In viewdidunload i am setting self.tableview=nil , is there anyway to release the images held by the cell as memory footprint keeps growing even when navigating to totally different viewcontroller. Memory shoots up only when selecting the viewcontroller that holds this tableview.
The reason for crash is that you're releasing NSData object which you shouldn't.
And the scrolling of the table should be slow always because with each scroll, it will call cellForRowAtIndexPath method & with it will create a new image.
So try the below code & let me know whether it works or not
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *FavouritesCellIdentifier = #"cellIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:cellIdentifier] autorelease];
UIImageView* imgView = [[UIImageView alloc] initWithFrame:CGRectMake(10, 16, 64, 64)];
imgView.tag = kImageLabelTag;
[cell.contentView addSubview:imgView];
[imgView release];
NSData *contactImageData = (NSData*)ABPersonCopyImageDataWithFormat(personRef, kABPersonImageFormatThumbnail);
UIImage *img = [[UIImage alloc] initWithData:contactImageData];
[imgView setImage:img];
[img release];
}
return cell;
}
I thing that the fact that you cast your CFDataRef to a NSData is the problem. I guess the release method doesn't do anything since the pointer is actually a pointer to a CFDataRef object that is supposed to be released using CFRelease function.
Try :
UIImageView* imgView = (UIImageView*)[cell viewWithTag:kImageLabelTag];
CFDataRef contactImageData = ABPersonCopyImageDataWithFormat(personRef,
kABPersonImageFormatThumbnail);
UIImage *img = [[UIImage alloc] initWithData:(NSData*)contactImageData];
[imgView setImage:img];
CFRelease(contactImageData);
[img release];