imageDownloadingQueue is not working - iphone

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.

Related

Want to show UIActivityIndicator on UITableViewCell while setting images on UITableViewCell

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.

my imageviews are not behaving properly in the table view?

I have a table view loading in the below manner. If a condition is met I need to add a particular image above the cell.imageview. Also the images are coming in different dimensions. Below is my code can anybody point me in where i am going wrong.
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
}
if(array==nil||[array count]==0)
{
}
else
{
NSMutableDictionary *dicttable=[array objectAtIndex:indexPath.row];
NSString *head=[dicttable objectForKey:#"name"];
NSString *type=[dicttable objectForKey:#"type"];
NSString *imgUrl = [dicttable objectForKey:#"image"];;
if(imgUrl!=nil)
{
if(![[ImageCache sharedImageCache] hasImageWithKey:imgUrl])
{
cell.imageView.image = [UIImage imageNamed:#"noimage_icon.png"];
NSArray *myArray = [NSArray arrayWithObjects:cell.imageView,imgUrl,#"noimage_icon.png",[NSNumber numberWithBool:NO],nil];
AppDelegate *appDelegate = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[appDelegate performSelectorInBackground:#selector(updateImageViewInBackground:) withObject:myArray];
cell.imageView.frame=CGRectMake(0,0,48,48);
cell.imageView.bounds=CGRectMake(0,0,48,48);
[cell.imageView setClipsToBounds:NO];
}
else
{
cell.imageView.image = [[ImageCache sharedImageCache] getImagefromCacheOrUrl:imgUrl];
cell.imageView.frame=CGRectMake(0,0,48,48);
cell.imageView.bounds=CGRectMake(0,0,48,48);
[cell.imageView setClipsToBounds:NO];
}
}
else
{
cell.imageView.image = [UIImage imageNamed:#"noimage_icon.png"];
}
if([type isEqualToString:#"YES"])
{
UIImageView* img = [[UIImageView alloc]initWithImage:[UIImage imageNamed:#"bluel.png"]];
[cell setBackgroundView:img];
[img release];
cell.textLabel.text = head;
cell.textLabel.backgroundColor = [UIColor clearColor];
cell.detailTextLabel.textColor=[UIColor grayColor];
cell.detailTextLabel.text = subtitle1;
cell.detailTextLabel.backgroundColor = [UIColor clearColor];
}
else
{
UIImageView* img = [[UIImageView alloc]initWithImage:[UIImage imageNamed:#"wnew1.png"]];
[cell setBackgroundView:img];
[img release];
cell.textLabel.text = head;
cell.textLabel.backgroundColor = [UIColor clearColor];
[cell.imageView addsubview:spclimage]
cell.textLabel.text = head;
cell.textLabel.backgroundColor = [UIColor clearColor];
cell.detailTextLabel.textColor=[UIColor grayColor];
cell.detailTextLabel.text = subtitle1;
cell.detailTextLabel.backgroundColor = [UIColor clearColor];
}
}
return cell;
Here the problem is only in the last row the special image is adding. Not in all rows. Also the image view size is different in all the time the tableview is reloading?
A couple of thoughts:
The updateImageViewInBackground seems suspect, as the name suggests that you're updating an image view, but you don't specify which cell you're updating.
I also see you doing a addSubview:spclimage. Obviously, if that spclimage was on another cell, as soon as you do addSubview, it will be removed from the previous location before being added to current cell. In fact, just the notion of adding an image as a subview of an existing imageview is curious.
If your cache doesn't have the image yet, I see where you're initializing the image with noimage_icon.png, but I don't see where you're actually updating the image view. You say updateImageViewInBackground is "then updating the image". Do you mean setting the image property for the imageView for this cell for this indexPath? Or maybe updating spclimage? If so, that's problematic.
The typical pattern (using GCD) for this would be:
cell.imageView.image = [UIImage imageNamed:#"noimage_icon.png"];
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *image = ... // do whatever you need to do to get the image, load cache, etc.
// ok, now that you have the image, dispatch the update of the UI back to the main queue
dispatch_async(dispatch_get_main_queue(), ^{
// because we're doing this asynchronously, make sure the cell is still
// visible (it could have scrolled off and the cell was dequeued and
// reused), so we're going to ask the tableview for the cell for that
// indexPath, and it returns `nil` if it's not visible. This method is
// not to be confused with the similarly named `UITableViewControllerDelegate`
// method.
UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
// if the image view is still visible, update it
if (cell)
{
cell.imageView.image = image;
}
});
});

UITableViewCell with image lags

guys, I have UITableView with some images, which are loading from internet. Images is very little, but my tableview isn't work perfect.
My code:
static NSString *CellIdentifier = #"Cell";
TDBadgedCell *cell = [[[TDBadgedCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
cell.badgeColor = [UIColor colorWithRed:((float)128 / 255.0f) green:((float)161 / 255.0f) blue:((float)176 / 255.0f) alpha:1];
NSDictionary *dict = nil;
if (searching == NO) {
dict = [companies objectAtIndex:indexPath.row];
} else {
dict = [copyListOfItems objectAtIndex:indexPath.row];
}
cell.badgeString = [NSString stringWithFormat:#"%#",[dict objectForKey:#"rating"]];
cell.textLabel.numberOfLines = 2;
cell.textLabel.text = [dict objectForKey:#"title"];
cell.textLabel.textColor = [UIColor colorWithRed:((float)80 / 255.0f) green:((float)80 / 255.0f) blue:((float)80 / 255.0f) alpha:1];
cell.textLabel.font = [UIFont fontWithName:#"Verdana" size:17.0];
UIImage *img = [[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:[dict objectForKey:#"image"]]]] scaleToFitSize:CGSizeMake(16, 16)];
cell.imageView.image = img;
[cell setAccessoryType:UITableViewCellAccessoryDisclosureIndicator];
return cell;
TDBadgedCell can't be reason, because without images it works perfect.
I'm using open sourse classes to resize UIImage. You can found it here.
Have you any ideas?
You could do the image downloading part in another thread so it won't block the main. You can do this like this:
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0ul), ^{
// download the images here
dispatch_async(dispatch_get_main_queue(), ^{
// add them to your cell here
});
});
So you need to switch to another thread for downloading and then get back to the main thread and add them to the UI. UI elements must only be handled with in the main thread.
I have posted code that shows you to deal with this. We have a class MyImageDownloader that downloads an image on request and posts a notification when it arrives:
https://github.com/mattneub/Programming-iOS-4-Book-Examples/blob/master/p754p772downloader/p754downloader/MyImageDownloader.m
The table view data source supplies a placeholder if a MyImageDownloader has no image, and supplies the image if it has one:
https://github.com/mattneub/Programming-iOS-4-Book-Examples/blob/master/p754p772downloader/p754downloader/RootViewController.m
Thus, all we have to do is reload the table when an image arrives. m.

Jerky scrolling with only a few images

I'm using the method that Apple shows on using subviews in table (most of what is below is from their documentation). I am only pulling in via an rss feed about 12 images, but this results in slow scrolling - if I get rid of the images it moves smoothly. The images are not big, so that can't be the problem. Before looking into more involved solutions (background processing, etc.), is there anything I can do to make this work better?
Thanks for any help you can give on this.
#define MAINLABEL_TAG 1
#define SECONDLABEL_TAG 2
#define PHOTO_TAG 3
-(UITableViewCell *)tableView : (UITableView *)tableView cellForRowAtIndexPath : (NSIndexPath *)indexPath {
UILabel * mainLabel, * secondLabel;
UIImageView * photo;
static NSString * CellIdentifier = # "Cell";
UITableViewCell * cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
mainLabel = [[[UILabel alloc] initWithFrame:CGRectMake(100.0, 0.0, 210.0, 0.0)] autorelease];
mainLabel.tag = MAINLABEL_TAG;
mainLabel.font = [UIFont systemFontOfSize:14.0];
mainLabel.textAlignment = UITextAlignmentRight;
mainLabel.textColor = [UIColor blackColor];
mainLabel.opaque = YES;
mainLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleHeight;
[cell.contentView addSubview : mainLabel];
secondLabel = [[[UILabel alloc] initWithFrame:CGRectMake(90.0, 30.0, 220.0, 0.0)] autorelease];
secondLabel.tag = SECONDLABEL_TAG;
secondLabel.font = [UIFont systemFontOfSize:12.0];
secondLabel.textAlignment = UITextAlignmentRight;
secondLabel.textColor = [UIColor darkGrayColor];
secondLabel.opaque = YES;
secondLabel.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleHeight;
[cell.contentView addSubview : secondLabel];
photo = [[[UIImageView alloc] initWithFrame:CGRectMake(30.0, 3.0, 50.0, 40.0)] autorelease];
photo.tag = PHOTO_TAG;
photo.opaque = YES;
photo.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleHeight;
[cell.contentView addSubview : photo];
} else {
mainLabel = (UILabel *)[cell.contentView viewWithTag : MAINLABEL_TAG];
secondLabel = (UILabel *)[cell.contentView viewWithTag : SECONDLABEL_TAG];
photo = (UIImageView *)[cell.contentView viewWithTag : PHOTO_TAG];
}
// Configure the cell.
mainLabel.text = [[items objectAtIndex:indexPath.row] objectForKey:# "title"];
secondLabel.text = [[items objectAtIndex:indexPath.row] objectForKey:# "teacher"];
NSString * path = [[items objectAtIndex:indexPath.row] objectForKey:# "audimage"];
NSString * mypath = [path stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSURL * url = [NSURL URLWithString:mypath];
NSData * data = [NSData dataWithContentsOfURL:url];
UIImage * img = [[UIImage alloc] initWithData:data];
photo.image = img;
return cell;
[cell release];
}
It looks like every time you're configuring a cell, you re-download the entire image from the web (or reading from the hard disk, if that's where your URL points to):
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *img = [[UIImage alloc] initWithData:data];
This is really slow! The way UITableView works is by reusing cells - every time a cell goes off-screen, it may be given back to the cellForRowAtIndexPath method to be used as the next cell that becomes visible.
This means that every time a new cell becomes visible, you're downloading and creating its image.
Instead, you have two options:
Download and store all the images first (if there are only 12, it probably won't take too much memory).
Download the images as-needed, and if you get a memory warning, dump some of the cached images. This is a bit harder to implement, so I'd try the first solution first.

Memory leak loading thumbnail image

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