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];
Related
I have a table with horizontal and vertical scrolling in UITableView,and each cell has a UIImageView and i am setting image on cell's UIImageView by URL.I want to show the ActivityIndicator on all cells until the images is downloaded.I have search a lot but didn't find anything good.
Here is my code for CellForRowAtIndexPath
static NSString *CellIdentifier = #"ArticleCell";
NSDictionary *dict=[self.articles objectAtIndex:indexPath.row];
__block ArticleCell_iPhone *cell = (ArticleCell_iPhone *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
//added for uiacticvaty indicator on uitableviewcell
UIActivityIndicatorView *loadingActivity = [[UIActivityIndicatorView alloc]
initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
[loadingActivity startAnimating];
loadingActivity.frame = cell.thumbnail.frame;
loadingActivity.frame = CGRectMake(0, 0, 90 , 90);
[loadingActivity setBackgroundColor:[UIColor greenColor]];
UIView* parent = [cell.thumbnail superview];
[parent addSubview:loadingActivity];
[parent bringSubviewToFront:loadingActivity];
//
//added for uiacticvaty indicator
if (cell == nil)
{
cell=[[[ArticleCell_iPhone alloc]initWithFrame:CGRectMake(0, 0, kCellWidth_iPad, kCellHeight_iPad) withNSDict:dict] autorelease];
__block NSDictionary *currentArticle = [self.articles objectAtIndex:indexPath.row];
dispatch_queue_t concurrentQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(concurrentQueue, ^{
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[currentArticle objectForKey:#"thumbnail_url"]]]];
dispatch_async(dispatch_get_main_queue(), ^{
[cell.thumbnail setImage:image];
// [loadingActivity stopAnimating];
cell.titleLabel.text = [currentArticle objectForKey:#"Title"];
});
});
}
// [cell.imagefooter setImage:[UIImage imageNamed:#"line"]];
return cell;
}
Consider leveraging open source solutions like SDWebImage (with UIActivityIndicator-for-SDWebImage).
It´s necessary to have a "background" to the cell.imageView... But must be a image, not just a frame. So just create a UIImage as a "white background" and set in cell.imageView.image. The code will be:
UIActivityIndicatorView *spinner = [[UIActivityIndicatorView alloc]
initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleGray];
UIImage *whiteback = [[UIImage alloc] initWithContentsOfFile:#"whiteback.png"];
cell.imageView.image = whiteback;
[cell.imageView addSubview:spinner];
[spinner startAnimating];
cell.textLabel.text=#"Carregando...";
[whiteback release];
[spinner release];
The whiteback.png is just a 25x25 pixels white square...
Thanks
Give tag to your activity indicator using custom cell.
UITableViewCell *aCell=[tableView dequeueReusableCellWithIdentifier:#"MyIdentifier"];
UIActivityIndicatorView *aActivity=(UIActivityIndicatorView *)[aCell viewWithTag:1];
[aActivity startAnimating];
return aCell;
I tried this code in my demo and its working perfectly.
I am using NSOperationQueue for caching images in background.
Here is the code below:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *cellIdentifier = #"CellIdentifier";
UITableViewCell *cell;
cell = [self.mUpcomEventsTable dequeueReusableCellWithIdentifier:cellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
}
cell.selectionStyle = UITableViewCellSelectionStyleNone;
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
UIImageView *imgView;
UILabel *lblEventName;
UILabel *lblDate;
UILabel *lblTime;
if(self.mEventNameArr != NULL)
{
NSString *imageUrlString = [NSString stringWithFormat:#"%#", [self.mEventImageArr objectAtIndex:indexPath.row]];
UIImage *cachedImage = [self.imageCache objectForKey:imageUrlString];
NSLog(#"cache:%#", self.imageCache);
imgView = [[UIImageView alloc]initWithFrame:CGRectMake(0, 0, 80, 90)];
lblEventName = [[UILabel alloc]initWithFrame:CGRectMake(90, 10, 200, 30)];
lblEventName.text = [self.mEventNameArr objectAtIndex: indexPath.row];
lblEventName.font = [UIFont fontWithName:#"Helvetica-Bold" size:18];
lblDate = [[UILabel alloc]initWithFrame:CGRectMake(90, 50, 100, 30)];
lblDate.text = [self.mEventDateArr objectAtIndex:indexPath.row];
lblTime = [[UILabel alloc]initWithFrame:CGRectMake(190, 50, 100, 30)];
lblTime.text = [self.mEventTimeArr objectAtIndex:indexPath.row];
strEventName = lblEventName.text;
strEventDate = lblDate.text;
strEventTime = lblTime.text;
if (cachedImage)
{
imgView.image = cachedImage;
}
else
{
// you'll want to initialize the image with some blank image as a placeholder
imgView.image = [UIImage imageNamed:#"Placeholder.png"];
// now download in the image in the background
NSLog(#"queue:%#",self.imageDownloadingQueue);
[self.imageDownloadingQueue addOperationWithBlock:^{
NSURL *imageUrl = [NSURL URLWithString:imageUrlString];
NSData *imageData = [NSData dataWithContentsOfURL:imageUrl];
UIImage *image = nil;
if (imageData)
image = [UIImage imageWithData:imageData];
if (image)
{
[self.imageCache setObject:image forKey:imageUrlString];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{
UITableViewCell *updateCell = [tableView cellForRowAtIndexPath:indexPath];
if (updateCell)
imgView.image = cachedImage;
}];
}
}];
}
}
else cell.textLabel.text = #"No event";
[cell addSubview:imgView];
[cell addSubview:lblEventName];
[cell addSubview:lblDate];
[cell addSubview:lblTime];
return cell;
}
It is not going in this line
[self.imageDownloadingQueue addOperationWithBlock:^{
it go outside from this. Why so, please help for above.
Took idea from here link.
There are a lot of disadvantage of using NSOperationQueue. One of them is your image will flick when every time you scroll the UITableView.
I'd suggest you to use this AsyncImageView. I've used it and it work wonders. To call this API:
ASyncImage *img_EventImag = alloc with frame;
NSURL *url = yourPhotoPath;
[img_EventImage loadImageFromURL:photoPath];
[self.view addSubView:img_EventImage]; // In your case you'll add in your TableViewCell.
It's same as using UIImageView. Easy and it does most of the things for you. AsyncImageView includes both a simple category on UIImageView for loading and displaying images asynchronously on iOS so that they do not lock up the UI, and a UIImageView subclass for more advanced features. AsyncImageView works with URLs so it can be used with either local or remote files.
Loaded/downloaded images are cached in memory and are automatically cleaned up in the event of a memory warning. The AsyncImageView operates independently of the UIImage cache, but by default any images located in the root of the application bundle will be stored in the UIImage cache instead, avoiding any duplication of cached images.
The library can also be used to load and cache images independently of a UIImageView as it provides direct access to the underlying loading and caching classes.
I am setting a UIImage, via a UIImageView as my background view of a subclassed UITableViewCell like so:
-(void)awakeFromNib {
UIImage *backImg = [[UIImage imageNamed:#"CellBackground"]
resizableImageWithCapInsets:UIEdgeInsetsMake(16, 132, 16, 16)];
UIImageView *imv = [[UIImageView alloc] initWithImage:backImg];
self.backgroundView = imv;
}
This works excellently, each cell is different in height (exposed via heightForRowAtIndexPath which calculates the height of a UILabel with text in it), and the background image resizes as I want the cell to.
However, when I rotate the device, the view hangs mid rotate, and takes 5-10 seconds to redraw in landscape, or crashes with no error. If I remove this imageview from the backgroundView, the rotate works excellently. Both simulator and device.
[Edit] Alternatively, I added the imageview as a subview of the cell.contentView - performance was better, but still laggy.
UIImage *backImg = [[UIImage imageNamed:#"CellBackground"]
resizableImageWithCapInsets:UIEdgeInsetsMake(16, 132, 16, 16)];
UIImageView *imv = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
imv.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
imv.image = backImg;
[self.contentView addSubview:imv];
Additionally, as mentioned above, my UITableViewCell is a subclass. The above code is in awakeFromNib, and I'm loading in my UITableViewCell like so:
// within initWithNibName: of UIViewcontroller:
cellLoader = [UINib nibWithNibName:#"MasterTableViewCell" bundle:[NSBundle mainBundle]];
// and UITableView method:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
MasterTableViewCell *cell = (MasterTableViewCell *)[tableView dequeueReusableCellWithIdentifier:#"MasterTableViewCell"];
if (cell == nil) {
NSArray *topLevelItems = [cellLoader instantiateWithOwner:self options:nil];
cell = [topLevelItems objectAtIndex:0];
}
return cell;
}
Am I doing something wrong? Any hints?
Are you calling this every time you draw a cell? This can really hurt your performance. I would suggest only doing this when drawing a new cell. So it would look like
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil)
{
UIImage *backImg = [[UIImage imageNamed:#"CellBackground"]
resizableImageWithCapInsets:UIEdgeInsetsMake(16, 132, 16, 16)];
UIImageView *imv = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
imv.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;
imv.image = backImg;
[self.contentView addSubview:imv];
}
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.
Edit----- i done the same code on english record scrolling speed remain fast as usual and its working fine, but when i am fetching Arabic data scrolling is slow again. is this a problem with arabic data???
I have records of about 100 and my tableview scrolling is very slow. can anyone tell me what is wrong with this code, and why iam getting slow scrolling?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
GreetingAppDelegate *appdelegate = (GreetingAppDelegate *)[[UIApplication sharedApplication]delegate];
DBSetter *product = (DBSetter *)[appdelegate.myrecords objectAtIndex:indexPath.row];
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
cell.textLabel.font=[UIFont fontWithName:#"Arial" size:16.0];
}
CGRect a=CGRectMake(8, 0, 307, 59);
UIImageView *aImg=[[UIImageView alloc] initWithFrame:a];
UIImageView *bImg=[[UIImageView alloc] initWithFrame:a];
//if(indexPath.row%2==0)
aImg.image=[UIImage imageNamed:#"cell-arrow.png"];
//else {
// aImg.image=[UIImage imageNamed:#"CellHighlight.png"];
//}
bImg.image=[UIImage imageNamed:#"cell-arrow-h.png"];
[aImg setContentMode:UIViewContentModeScaleToFill];
[bImg setContentMode:UIViewContentModeScaleToFill];
cell.backgroundView=aImg;
cell.selectedBackgroundView=bImg;
[aImg release];
[bImg release];
NSString *tempStr=[NSString stringWithFormat:#"%#",product.tempdesc];
//int stringlength=[tempStr length];
//[[tempStr stringByReplacingOccurrencesOfString:#"<Dear User>" withString:#" "] substringWithRange:NSMakeRange(0, 20)];
//if (stringlength >20) {
//cell.textLabel.text = [NSString stringWithFormat:#"%#...", [[tempStr stringByReplacingOccurrencesOfString:#"<Dear user>" withString:#""] substringWithRange:NSMakeRange(0, 30)]];
//}
//else {
cell.textLabel.text = tempStr;
//}
if(appdelegate.lang==2)
{
[cell setTextAlignment:UITextAlignmentRight];
aImg.transform = CGAffineTransformMakeScale(-1, 1);
bImg.transform = CGAffineTransformMakeScale(-1, 1);
}
return cell;
}
A posible cause could be you're creating the imageView's outside the cell creation block. Try this
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
cell.textLabel.font=[UIFont fontWithName:#"Arial" size:16.0];
CGRect a=CGRectMake(8, 0, 307, 59);
UIImageView *aImg=[[UIImageView alloc] initWithFrame:a];
UIImageView *bImg=[[UIImageView alloc] initWithFrame:a];
aImg.image=[UIImage imageNamed:#"cell-arrow.png"];
bImg.image=[UIImage imageNamed:#"cell-arrow-h.png"];
[aImg setContentMode:UIViewContentModeScaleToFill];
[bImg setContentMode:UIViewContentModeScaleToFill];
cell.backgroundView=aImg;
cell.selectedBackgroundView=bImg;
[aImg release];
[bImg release];
}
First thing I see: you're not taking advantage of the recycling!
Do as much as possible in the if (cell == nil) block. Avoid creating image views and other things like that each time an item scroll.
The only meaningful code for recycled cells I see is the cell.textLabel.text = tempStr; line.
Have you tried making a custom TableViewCell with that image as background? You can change the background upon select in the setSelected function of the custom TableViewCell.
Thanks every one for your replies... i just figure out it my self. i just use length and range to make it works. the code that i used is as follow, for the one who might suffer same problem.
NSString *tempStr=[NSString stringWithFormat:#"%#",product.tempdesc];
int stringlength=[tempStr length];
if (stringlength >20) {
cell.textLabel.text = [NSString stringWithFormat:#"%#...", [[tempStr stringByReplacingOccurrencesOfString:#"<Dear user>" withString:#""] substringWithRange:NSMakeRange(0, 30)]];
}
else {
cell.textLabel.text = tempStr;
}