I've created a custom UITableViewCell and each cell is passed a custom subclass of AVPlayer object from the UITableViewController. On each cell I have a play button, pause button and loading indicator.
When I play the audio, elements work as required, and change when player state has changed e.g. when playing, pause button appears, play button disappears. When I play audio on a second cell, the first cell knows this, resets it button state, and the second cell does it's business.
So this functionality works perfectly, the only problem is because the UITableViewCells get reused, when I scroll down to cells below, I start seeing the pause button appear on them. This is because they are the same cells as the ones above (reused) and because my cells are delegates for my custom subclass of AVPlayer, the audio player is sending messages to a cell which isn't the correct one.
What can I do to make each UITableViewCell a separate delegate object for my AVPlayer?
You have to remove the elements from the cells upon reuse:
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
} else {
/* prepare for reuse */
[cell.playButton removeFromSuperview];
/* or */
[[cell viewWithTag:10] removeFromSuperview];
}
I had the same problem with images I'm loading from a JSON blob. I used GCD and saved my images to an NSDictionary paired with the key assigned to each cell.
- (UIImage *)imageForRowAtIndexPath:(NSIndexPath *)indexPath
{
// get the dictionary for the indexPath
NSDictionary *tweet = [tweets objectAtIndex:[indexPath row]];
// get the user dictionary for the indexPath
NSDictionary *user = [tweet objectForKey:#"posts"];
//get the image URL
NSURL *url = [NSURL URLWithString:[[[[[tweet objectForKey:#"photos"] objectAtIndex:0] objectForKey:#"alt_sizes"] objectAtIndex:3] objectForKey:#"url"]];
// get the user's id and check for a cached image first
NSString *userID = [user objectForKey:#"id"];
UIImage *image = [self.images objectForKey:userID];
if(!image)
{
// if we didn't find an image, create a placeholder image and
// put it in the "cache". Start the download of the actual image
image = [UIImage imageNamed:#"Placeholder.png"];
[self.images setValue:image forKey:userID];
//get the string version of the URL for the image
//NSString *url = [user objectForKey:#"profile_image_url"];
// create the queue if it doesn't exist
if (!queue) {
queue = dispatch_queue_create("image_queue", NULL);
}
//dispatch_async to get the image data
dispatch_async(queue, ^{
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *anImage = [UIImage imageWithData:data];
[self.images setValue:anImage forKey:userID];
UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
//dispatch_async on the main queue to update the UI
dispatch_async(dispatch_get_main_queue(), ^{
cell.imageView.image = anImage;
});
});
}
// return the image, it could be the placeholder, or an image from the cache
return image;
}
I solved the problem by making the UITableViewController the delegate for the audio player. Then saving the "currently playing" cell's indexPath to a #property in the UITableViewController.
Then in - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath I check to see if the indexPath is the same as the "currently playing" cell's indexPath, if so set the "audio playing" button arrangent, if not then set the default button arrangement.
This works better in distinguishing cells as you have a unique identifier indexPath to compare them with.
Related
I have to display image in tableview,i got all images but it does not display. Here Array contains 3 images, these images came from server. when cell for row at indexpath call it display only 3rd image that is last image 1st and 2nd row will be blank but when it scroll my tableview from bottom to top than only 1st and 2nd image displayed.
-
(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell *cell = nil;
static NSString *CellIdentifier = #"Cell";
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.selectionStyle = UITableViewCellSeparatorStyleNone;
cell.backgroundColor = [UIColor clearColor];
if (appDelegate.array_xml != (id)[NSNull null])
{
ObjMore = [appDelegate.array_xml objectAtIndex:indexPath.row];
//imageview
NSString *str_img = ObjMore.iconurl;
str_img = [str_img stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
NSLog(#"str_img: %#", str_img);
self.imageicon = [[UIImageView alloc]initWithFrame:CGRectMake(10, 10, 50, 50)];
NSURL *url = [NSURL URLWithString:str_img];
NSLog(#"url %#",url);
[[AsyncImageLoader sharedLoader]cancelLoadingURL:url];
self.imageicon.imageURL = url;
self.imageicon.userInteractionEnabled = YES;
self.imageicon.tag = indexPath.row;
self.imageicon.backgroundColor = [UIColor clearColor];
[cell.contentView addSubview:self.imageicon];
}
return cell;
}
Please Help.
Thanks in Advance.
Please change your code -
[[AsyncImageLoader sharedLoader]cancelLoadingURL:self.imageicon.imageURL];
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.
You create the object AsyncImageView instead of UIImageView
Are you refreshing the imageview or reloading the table row once you get the image ?
Also make sure you are refreshing the UI in main thread.
In my app, when user presses on the one of the tabs in UITabBar, it takes too much time to load view and show it to the user, so it may be confusing (it's because I load images from the web in the UITableView). So, I decided to use multithreading to show view before all the images finished loading.
I am using this code:
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[SetsCustomCell alloc] initWithFrame:CGRectZero];
}
// getting url
NSURL* imgUrl = [[NSURL alloc]initWithString:[[mainArray objectAtIndex:
indexPath.row]valueForKey:#"imageURL"]];
//put this url and current cell in the dictionary
NSDictionary* params = [NSDictionary dictionaryWithObjectsAndKeys:
imgUrl,#"localUrl",cell,#"localCell", nil];
// multithreading time (calling loadImageWithParams method)
[self performSelectorInBackground:#selector(loadImageWithParams:)
withObject:params];
return cell;
}
-(void)loadImageWithParams:(NSDictionary*)params {
NSURL* url = [params objectForKey:#"localUrl"];
cell = [params objectForKey:#"localCell"];
UIImage* thumb = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
NSDictionary* backParams = [NSDictionary dictionaryWithObjectsAndKeys:
cell,#"localCell",thumb,#"thumb", nil];
[self performSelectorOnMainThread:#selector(setImage:)
withObject:backParams waitUntilDone:YES];
}
-(void)setImage:(NSDictionary*)params{
cell = [params objectForKey:#"localCell"];
UIImage* thumb = [params objectForKey:#"thumb"];
[cell.imageView setImage:thumb];
cell.imageView.hidden = NO;
[cell setNeedsLayout];
}
I have only two cells in the UITableView and the problem is that only second cell loads its image. The first cell is still empty. However, if I scroll the UITableView until the first cell is no longer visible, and it calls cellForRowAtIndexPath: again, first cell gets its image.
I have also tried to make multithreading with NSOperationQueue and GCD but have the same results.
It seems like I don't clearly understand how multithreading works but I will be veeeery grateful if someone will point me at my mistake.
Thanks!
The way I've done this in the past is to create an NSObject subclass to store the images. Then the datastore for the UITableView is an array of these objects instead of URL paths.
Then you can lazy load the image like this...
ObjectModel.h
#property (nonatomic, strong) UIImage *image;
ObjectModel.m
- (UIImage*)image
{
if (_image == nil) {
[self downloadImage];
return [UIImage imageNamed:#"placeholderImage"];
}
return _image;
}
- (void)downloadImage
{
//Put your async stuff here to download the image then reload the tableView when it's done.
}
By doing this you are storing the images on the datastore but also managing the download stuff in its own class and keeping your TVC clean.
It seems like you're overcomplicating this -- I don't really understand what you're doing with the params dictionary. Why not present the controller, then start your download in viewDidAppear. When the results come back, populate your array with them, and call reloadData. Do the cells have anything in them besides the images that you can load right away?
Typical practice for this is to implement a LazyUIImage class which downloads/loads the image in the background. While the image is downloading, display something like a UIActivityIndicator. When the image arrives in the LazyUIImage, send out a notification and stop the activity indicator (and remove it from the view perhaps). For every view controller that contains a LazyUIImage, handle that notification, and e.g., perform [myTable reloadData]. In LazyUIImage, write a method called getImage which returns nil while the image is loading, and after the image has loaded, return the image.
Edit: actually, if you implement the LazyUIImage as a LazyUIImageView then you don't need to reload anything in containing view controllers: in the LazyUIImageView place a top level background view, and then switch out the UIActivityIndicator child view for the actual UIImageView once the image finishes downloading.
You need to do Lazy loading stuff.. Search for lazy loading using NSOperationQueue.
I've been banging my head against the wall on this one and searched far and wide for a solution to no avail:
I have a large array of data pulled from the web and I'm using Loren Brichter's ABTableViewCell to make it run smoothly by drawing everything inside of the contentView of each cell to avoid UILabels and UIImageViews slowing scrolling down.
This works great for displaying text, but I run into a problem with images because of the time it takes to download them. I can't seem to find a way to force the contentView of each cell displayed to redraw itself once the corresponding image has been downloaded. I must point out I am not drawing labels and imageViews, but just the contentView in order to save memory.
Right now the table behaves like this:
Load: text displayed, no images
Scroll up or down: images finally
show up once the cells move off screen
A sample project is here
Code:
ABTableViewCell.h
#interface ABTableViewCell : UITableViewCell
{
UIView *contentView;
}
ABTableViewCell.m
- (void)setNeedsDisplay
{
[contentView setNeedsDisplay];
[super setNeedsDisplay];
}
- (void)drawContentView:(CGRect)r
{
// subclasses implement this
}
TableCellLayout.h
#import "ABTableViewCell.h"
#interface TableCellLayout : ABTableViewCell {
}
#property (nonatomic, copy) UIImage *cellImage;
#property (nonatomic, copy) NSString *cellName;
TableCellLayout.m
#import "TableCellLayout.h"
#implementation TableCellLayout
#synthesize cellImage, cellName;
- (void)setCellName:(NSString *)s
{
cellName = [s copy];
[self setNeedsDisplay];
}
- (void)setCellImage:(UIImage *)s
{
cellImage = [s copy];
[self setNeedsDisplay];
}
- (void)drawContentView:(CGRect)r
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextFillRect(context, r);
[cellImage drawAtPoint:p];
[cellName drawAtPoint:p withFont:cellFont];
}
TableViewController.m
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
TableCellLayout *cell = (TableCellLayout *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil)
{
cell = [[TableCellLayout alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.cellName = [[array valueForKey:#"name"] objectAtIndex:indexPath.row];
cell.cellImage = [UIImage imageNamed:#"placeholder.png"]; // add a placeholder
NSString *imageURL = [[array valueForKey:#"imageLink"] objectAtIndex:indexPath.row];
NSURL *theURL = [NSURL URLWithString:imageURL];
if (asynchronousImageLoader == nil){
asynchronousImageLoader = [[AsynchronousImages alloc] init];
}
[asynchronousImageLoader loadImageFromURL:theURL];
cell.cellImage = asynchronousImageLoader.image;
return cell;
}
This is the final method the AsynchronousImageLoader calls once the image is prepared:
- (void)setupImage:(UIImage*)thumbnail {
self.image = thumbnail;
[self setNeedsLayout];
}
I just need the correct way to tell my visible cells to redraw themselves once the row's image has been downloaded. I imagine I should be putting something in that final method (setupImage)--but I can't seem to get it working the way it should. Thoughts? Many thanks!
Final edit: the solution
Right, so as suspected, the problem was that visible cells weren't being told to redraw and update to the downloaded image once the call was complete.
I used the help provided by the answers below to put together a solution that works well for my needs:
Added a callback in the final method that the asynchronous image downloader calls:
AsyncImageView.m
- (void)setupImage:(UIImage*)thumbnail {
self.cellImage = thumbnail;
[cell setNeedsDisplay];
}
Note: I also set a local placeholder image in the initialization of the image downloader class just to pretty things up a bit.
Then in my cellForRowAtIndexPath:
NSURL *theURL = [NSURL URLWithString:imageURL];
AsyncImageView *aSync = [[AsyncImageView alloc] initWithFrame:CGRectMake(0, 0, 20, cell.bounds.size.height)];
[aSync loadImageFromURL:theURL];
cell.cellImageView = aSync;
return cell;
There may have been one or two other tweaks, but those were the major problems here. Thanks again SO community!
Make sure that you are updating the cell image in the Main Thread. UI updates only appear if done there, which is why you only see the update when you touch & scroll.
if(cell) {
dispatch_async(dispatch_get_main_queue(), ^{
cell.cellImage = thumbnail;
[cell setNeedsDisplay];
});
}
[EDIT]
You need the cell to be the delegate of the image loader and own the async redraw mechanism.
......
AsynchronousImages *asynchronousImageLoader = [[AsynchronousImages alloc] init];
asynchronousImageLoader.delegate = cell;
[asynchronousImageLoader loadImageFromURL:theURL];
return cell;
}
And place the delegate call back code in the cell implementation.
- (void)setupImage:(UIImage*)thumbnail {
self.cellImage = thumbnail;
}
You can used the Apple TableView Lazy Loading. They have sample codes that download images asynchonously. See link below
Apple LazyTableImages
On your end in AsynchronousImages class you can add an attribute NSIndexPath and the delegate on AsynchronousImages should be change. See the code below
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"Cell";
TableCellLayout *cell = (TableCellLayout *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if(cell == nil)
{
cell = [[TableCellLayout alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
cell.cellName = [[array valueForKey:#"name"] objectAtIndex:indexPath.row];
cell.cellImage = [UIImage imageNamed:#"placeholder.png"]; // add a placeholder
NSString *imageURL = [[array valueForKey:#"imageLink"] objectAtIndex:indexPath.row];
NSURL *theURL = [NSURL URLWithString:imageURL];
AsynchronousImages *asynchronousImageLoader = [[AsynchronousImages alloc] init];
asynchronousImageLoader.indexPath = indexPath;
[asynchronousImageLoader loadImageFromURL:theURL];
return cell;
}
//Delegate should be
- (void)setupImage:(UIImage*)thumbnail index:(NSIndexPath*) indePath {
TableCellLayout *cell = (TableCellLayout *) [tableView cellForRowAtIndexPath:indexPath];
if(cell) {
cell.cellImage = thumbnail;
[cell setNeedsDisplay];
}
}
verify UIImage creation and setting of the UIImageView's image property happen on the main thread. there is no reason setting the image view's image should not invalidate its rect if visible.
also confirm that your loads are cancelled correctly, if you are reusing cells.
Use AsyncImageView in place of uiimageview
You can tell your tableView to reloadData... or a slightly more refined reload:
[self.tableView reloadRowsAtIndexPaths:self.tableView.indexPathsForVisibleRows withRowAnimation:UITableViewRowAnimationNone];
Edit to Add (after looking at OP's source):
I've looked through your project, and the problem is with your architecture. You're misusing AsyncImageView because you're only using it to asynchronously load your image - whereas it is designed to both load, and display the image. This is why it has no 'callback' function to let you know when the data has been retrieved.
You would be better off replacing your CellLayout's image property with a UIImageView property instead. (Note that UIImageView is more efficient at drawing than image drawAtPoint anyway).
So:
Change your CellLayout class to use an UIImageView property instead of UIImage
Change your cellForRowAtIndexPath to set the AsyncImageView as a property directly on your Cell.
If you want to support placeholders, that should be added to your AsyncImageView class - so that it knows what to display while downloading the content.
cellForRowAtIndexPath:
NSURL *theURL = [NSURL URLWithString:imageURL];
AsyncImageView *aSync = [[AsyncImageView alloc] initWithFrame:CGRectMake(0, 0, 20, cell.bounds.size.height)];
[aSync loadImageFromURL:theURL];
cell.cellImageView = aSync;
return cell;
CellLayout.h
#property (nonatomic, strong) UIImageView *cellImageView;
CellLayout.m
- (void)setCellImageView:(UIImageView *)s
{
[cellImageView removeFromSuperview];
cellImageView = s;
[self addSubview:cellImageView];
}
I have used ASIHTTPRequest framework in my project to handle all network related tasks.
I have custom cell with thumbnail which is coming from web server and there are around 500 images so I have to reuse the cell to handle it. Due reusing of cell when we scroll through tableview we can see images of previous cells which will be replaced by new image.
If network connection is low its worse since it takes lot of time to download the image..so for that time you can see wrong image for particular because reusing cell so I need to find way so that this image replacement shouldn't be visible to user.
I am using ASIDownalod SharedCache method.
EDIT
NSString *reuseIdentifier = #"offerCell";
BRZOfferCell *offerCell = (BRZOfferCell*)[tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
if (offerCell==nil) {
offerCell = [[[BRZOfferCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier celltype:kDealCellTypeDealsList] autorelease];
}
[offerCell setImage:[UIImage imageNamed:IMAGE_NO_IMAGE]];
//---get the letter in the current section---
//NSString *alphabet = [mDealsIndex objectAtIndex:[indexPath section]];
//---get all deals beginning with the letter---
NSString* lSectionIndex = [NSString stringWithFormat:#"%i",[indexPath section]];
NSMutableArray *deals = [mIndexedOffersDic objectForKey:lSectionIndex];
if ([deals count]>0) {
//---extract the relevant deal from the deals array object---
Offer* lOffer = [deals objectAtIndex:indexPath.row];
[offerCell setOffer:lOffer];
offerCell.accessoryView = UITableViewCellAccessoryNone;
if (mTableView.dragging == NO && mTableView.decelerating == NO)
{
//Function : format image url to _thumb#2x.png and Initiate Image request download
//and set cache policy
[mListViewHelper InitImageRequest: lOffer.PromoImage indexPath: indexPath];
}
}
return offerCell;
As you said UITableView reuses cells in order to perform well, so you need to clear the cell before reuse it, or it's going to display the wrong data.
You also should use asynchronous calls, and some delegation to update cells.
I would actually take it a level higher and use NSOperationQueue, that allows you to set the maximum number of concurrent downloads, and canceling requests when leaving page.
What you might want to do is to create Data helpers
#protocol BookDataHelperDelegate <NSObject>
#required
- (void) bookDataHelperDidLoadImage:(BookDataHelper *)dataHelper;
#end
#interface BookDataHelper
#property (nonatomic, retian) UIImage *bookCover;
#property (nonatomic, retain) Book *book;
#property (nonatomic, assign) NSObject<BookDataHelperDelegate> *delegate;
- (void) fetchImageAsynchronouslyFromWebWithDelegate:(NSObject<BookDataHelperDelegate> *)delegate;
#end
This would be how you reload data on your table
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *SimpleTableIdentifier = #"SimpleTableIdentifier";
CustomCell *cell = [tableView
dequeueReusableCellWithIdentifier:SimpleTableIdentifier];
if (cell == nil)
{
cell = [[[CustomCell alloc]initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:SimpleTableIdentifier] autorelease];
}
BookDataHelper *dataHelper = [myArray objectAtIndex:indexPath.row];
if (!dataHelper.bookCover)
{
[cell.imageView setImage:nil];
[dataHelper fetchImageAsynchronouslyFromWebWithDelegate:self];
}
else
{
[cell.imageView setImage:dataHelper.bookCover];
}
cell.bookTitleLabel.text = dataHelper.book.title;
return cell;
}
- (void)bookDataHelperDidLoadImage:(BookDataHelper *)datahelper
{
[tableView reloadDate];
// here you would either reload the table completely
// Or you could reload specific cells
}
In your tableview cell delegate, when you get a reused or new, cell, clear the image before returning it. Update with the proper ownloaded image in an asynchronous callback. You might want to make sure the images are saved or retained somewhere else though if you don't want your app to keep redownloading them.
in ASIHTTPRequest framework its work on both type Synchronize and ASynchronize so firat tell me which one u use for get image data & also tell me that u send whole 500 image request at time or send as per your cell is loaded
or if you send 500 images request at a time than this on is not right as per the cell requirement send the request fro that cell image other wise its not feasible.
I have used ASIDownloadCache methods to solve my problem. Actually there are 2 solutions for this problem
Setting your own cache path instead of using SharedCache but i didn't went for this becuase I was already using sharedCache and found another efficient method which will avoid me changing my current implementation
In this approach I have used 2 methods from ASIDownloadCache methods(surprisingly ASIHTTPREquest website didn't mention these methods in their brief info)
2.1 First method - (BOOL)isCachedDataCurrentForRequest:(ASIHTTPRequest *)request
to verify if this particular image url is already cached or not if yes use 2nd method
2.2 - (NSData *)cachedResponseDataForURL:(NSURL *)url to get the cached image so that we can set the image in cellForRowAtIndexPath itself and you will not see image replacing issue due reusability of cell.
Here is the code :
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
NSString *reuseIdentifier = #"offerCell";
BRZOfferCell *offerCell = (BRZOfferCell*)[tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
if (offerCell==nil) {
offerCell = [[[BRZOfferCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier celltype:kDealCellTypeDealsList] autorelease];
}
[offerCell setImage:[UIImage imageNamed:IMAGE_NO_IMAGE]];
//---get the letter in the current section---
//NSString *alphabet = [mDealsIndex objectAtIndex:[indexPath section]];
//---get all deals beginning with the letter---
NSString* lSectionIndex = [NSString stringWithFormat:#"%i",[indexPath section]];
NSMutableArray *deals = [mIndexedOffersDic objectForKey:lSectionIndex];
if ([deals count]>0) {
//---extract the relevant deal from the deals array object---
Offer* lOffer = [deals objectAtIndex:indexPath.row];
[offerCell setOffer:lOffer];
offerCell.accessoryView = UITableViewCellAccessoryNone;
if ([mListViewHelper isCached:lOffer.PromoImage]) { // Is image available in Cache ?
// Image is available use image fomr cache directly
[offerCell setImage:[UIImage imageWithData:[mListViewHelper cacheDataWithNSURL:lOffer.PromoImage]]];
}
else{
//Function : Initiate Image request download and set cache policy
if (mTableView.dragging == NO && mTableView.decelerating == NO)
[mListViewHelper InitImageRequest: lOffer.PromoImage indexPath: indexPath];
}
}
return offerCell;
}
I am parsing an RSS feed, and then caching the images from the rss feed and then displaying them in the cell's imageview. However the method I am using, slows down the rss feed's parse time, and slows down the TableView's scroll time. Please could you tell me how I could speed up this process. One of the image links is: http://a1.phobos.apple.com/us/r1000/009/Video/95/5d/25/mzl.gnygbsji.71x53-75.jpg, and one of the rss feeds I am trying to parse is: http://itunes.apple.com/au/rss/topmovies/limit=50/xml. Here is the code I am using to cache the images:
- (UIImage )getCachedImage: (NSString)url
{
UIImage* theImage = [imageCache objectForKey:url];
if ((nil != theImage) && [theImage isKindOfClass:[UIImage class]]) {
return theImage;
}
else {
theImage = [UIImage imageWithData: [NSData dataWithContentsOfURL:[NSURL URLWithString: url]]];
[imageCache setObject:theImage forKey:url];
return theImage;
}
}
And the code I am using to get the images is:
- (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];
}
// Configure the cell...
int storyIndex = [indexPath indexAtPosition: [indexPath length] - 1];
int wierd = storyIndex *6;
cell.textLabel.text = [[stories objectAtIndex: storyIndex] objectForKey: #"songtitle"];
cell.detailTextLabel.text = [[stories objectAtIndex:storyIndex] objectForKey:#"artist"];
if ([imageLinks count] != 0) {
cell.imageView.image = [self getCachedImage:[imageLinks objectAtIndex:wierd]];
}
return cell;
}
As you can probably see, I am using an NSMutableArray called imageLinks, to store the imageLinks. However I am getting three image links from the rss feed, which means if I try to get the cached image: [imageLink objectAtIndex:storyIndex], the images are in the wrong places, but if I get the cached image: [imageLink objectAtIndex:wierd], it seems to work perfectly. So if you can see a fix to that, it would be great.
Thanks in advanced.
Your problem is that you're using dataWithContentsOfURL which is a blocking API. This means that it is performed on the main thread along with your UI and will block your UI until it completes. This is bad.
You should look into the NSURLConnection class and it's delegate protocol, NSURLConnectionDelegate to do data downloads asynchronously without manually spawning and managing new threads.