I need to implement the lazy loading concept for the images in my tableview, so that the user will be provided with the textual data initially and later the images.
How can i implement this in to my app.. help needed.. please
Thanks in advance
Shibin
What i have created for a project of mine works as follows;
Extend the UITableViewCell class by a category in "UITableViewCell+Async.h"
(See some examples if you are not sure what the category thing in Obj C is)
#interface UITableViewCell (Async)
-(void)loadAsyncImage:(NSString*)url withIndex:(NSInteger)index inWidth:(NSInteger)width inHeight:(NSInteger)height;
-(void)loadAsyncBackground:(NSMutableArray*)parameters;
#end
And then in the implementation file "UITableViewCell+Async.m"
#import "UITableViewCell+Async.h"
#implementation UITableViewCell (Async)
-(void)loadAsyncImage:(NSString*)url
withIndex:(NSInteger)index
inWidth:(NSInteger)width
inHeight:(NSInteger)height {
NSMutableArray* parameters = [NSMutableArray arrayWithCapacity:2];
[parameters addObject:url];
[parameters addObject:[NSNumber numberWithInteger:index]];
[parameters addObject:[NSNumber numberWithInteger:width]];
[parameters addObject:[NSNumber numberWithInteger:height]];
self.imageView.tag = index;
[self performSelectorInBackground:#selector(loadAsyncBackground:) withObject:parameters];
}
-(void)loadAsyncBackground:(NSMutableArray*)parameters {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSString* url = [parameters objectAtIndex:0];
NSInteger index = [[parameters objectAtIndex:1] integerValue];
NSInteger width = [[parameters objectAtIndex:2] integerValue];
NSInteger height = [[parameters objectAtIndex:3] integerValue];
UIImage* image = [Utils getImageResized:url inSize:CGSizeMake(width, height)];
if (self.tag==index) {
self.imageView.image = image;
[self setNeedsLayout];
}
[pool release];
}
#end
This basically adds a functionality to the UITableViewCell to load an image in a new background thread, resize the image and set it to the imageview. Checking the tag is added to see if the cell is still waiting for the image, since it can be re-used and another thread for the image could be downloading another image for that re-used cell...
The function in the above code with the signature of;
+(UIImage*)getImageResized:(NSString*)url inSize:(CGSize)size;
checks a local cache of images, downloads the image from the web if not in the cache, saves it to local cache, resizes the image in the given size and returns the image, all done in a sync(blocking) method call. Since this is already the background thread, no harm blocking it for this operation. When the method returns the image, it is set to the cell's imageview if it still has the same tag(not re-used for some other row)
In the cellForRowAtIndexPath method you can add the new category and you should be done;
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:#"......."];
if (cell == nil) {
................
}
................
[cell loadAsyncImage:deal.logo withIndex:indexPath.row inWidth:40 inHeight:40];
................
return cell;
}
Do you mean this
http://developer.apple.com/iphone/library/samplecode/LazyTableImages/Introduction/Intro.html
I used ASINetworkQueue for lazy loading.
In .h file
#import "ASINetworkQueue.h"
and define a variable
//array contains url address of images and a variable of ASINetworkQueue;
NSMutableArray *arrImg;
ASINetworkQueue *queue;
In .m file
static int rowNo = 0;
- (void)downloadImages{
if(queue == nil){
queue = [[[ASINetworkQueue alloc] init] autorelease];
}
for(NSString* urlString in arrImg)
{
NSURL *url = [NSURL URLWithString:urlString];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request setDelegate:self];
[request setDidFinishSelector:#selector(requestDone:)];
[request setDidFailSelector:#selector(requestWentWrong:)];
[queue addOperation:request];
}
[queue go];
}
- (void)requestDone:(ASIHTTPRequest*)req{
for (int i = 0; i < [arrImg count]; i++) {
NSMutableString *d = [[NSMutableString alloc] init];
d = [arrImg objectAtIndex:i];
//comparing the url to get correct row no of cell
if([[req.url absoluteString] isEqualToString:d]){
rowNo = i;
break;
}
}
//creating a cell with rowNo
NSIndexPath *index = [NSIndexPath indexPathForRow:rowNo inSection:0];
UITableViewCell *cell = (UITableViewCell *)[roomTable cellForRowAtIndexPath:index];
UIImageView *v = (UIImageView *)[cell.contentView viewWithTag:10];
v.image = [UIImage imageWithData:[req responseData]];
[cell release];
cell = nil;
[v release];
v = nil;
}
- (void)requestWentWrong:(ASIHTTPRequest*)req{
for (int i = 0; i < [arrImg count]; i++) {
NSMutableString *d = [[NSMutableString alloc] init];
d = [arrImg objectAtIndex:i];
//comparing the url to get correct row no of cell
if([[req.url absoluteString] isEqualToString:d]){
rowNo = i;
break;
}
}
NSLog(#"No data for cell : %d",rowNo);
NSLog(#"Request returned an error %#",[req error]);
}
Related
when I delete images from tablecells in UITableView it use to get delete from tablecells as well as from server but when we click to open the other image in egophotoviewer the deleted image is still coming?..
Here is my code.
- (void)showSelectedPhoto:(NSIndexPath *)indexPath {
NSString *passingImageName = [[self.tableDataSource objectAtIndex:indexPath.row]objectForKey:#"fileName"];
NSMutableArray *photoArray = [self getFilteredArray];
NSMutableArray *urlsArray = [[NSMutableArray alloc] init];
for (NSString *string in photoArray) {
NSLog(#"String Values:%#", string);
NSURL *imageUrl = [self getEnlargedImageImageUrl:[self._prefix stringByAppendingString:string]];
NSLog(#"Passing url is:%#", imageUrl);
photo = [[EGOQuickPhoto alloc] initWithImageURL:imageUrl name:string];
[urlsArray addObject:photo];
}
if (!source) {
self.source = [[EGOQuickPhotoSource alloc] initWithPhotos:urlsArray];
}
photoController = [[EGOPhotoViewController alloc] initWithPhotoSource:source];
[self.navigationController pushViewController:photoController animated:YES];
NSUInteger index = [photoArray indexOfObject:passingImageName];
[photoController moveToPhotoAtIndex:index animated:NO];
}
After deleting the image you need to reload the tableview.
[tableView reloadData];
I am using following code for loading images from server using following code.When i scroll UITableView application crashes.
AsynchrohousImageView class .m file
- (void)dealloc {
[connection cancel]; //in case the URL is still downloading
[connection release];
[data release];
[_imageView release];
[_activityIndicator release];
[super dealloc];
}
- (void)loadImageFromURL:(NSURL*)url
defaultImageName:(NSString *)defaultImageName
showDefaultImage:(BOOL)defaultImageIsShown
showActivityIndicator:(BOOL)activityIndicatorIsShown
activityIndicatorRect:(CGRect)activityIndicatorRect
activityIndicatorStyle:(UIActivityIndicatorViewStyle)activityIndicatorStyle {
if (connection!=nil) { [connection release]; } if (data!=nil) { [data release]; }
if ([[self subviews] count]>0) {
[[[self subviews] objectAtIndex:0] removeFromSuperview]; // }
if (defaultImageIsShown) {
self.imageView = [[[UIImageView alloc] initWithImage:[UIImage imageNamed:defaultImageName]] autorelease];
} else {
self.imageView = [[[UIImageView alloc] init] autorelease];
}
[self addSubview:_imageView];
_imageView.frame = self.bounds;
[_imageView setNeedsLayout];
[self setNeedsLayout];
if (activityIndicatorIsShown) {
self.activityIndicator = [[[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:activityIndicatorStyle] autorelease];
[self addSubview:_activityIndicator];
_activityIndicator.frame = activityIndicatorRect;
_activityIndicator.center = CGPointMake(_imageView.frame.size.width/2, _imageView.frame.size.height/2);
[_activityIndicator setHidesWhenStopped:YES];
[_activityIndicator startAnimating];
}
NSURLRequest* request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
}
- (void)connection:(NSURLConnection *)theConnection didReceiveData:(NSData *)incrementalData {
if (data==nil) { data = [[NSMutableData alloc] initWithCapacity:2048]; }
[data appendData:incrementalData];
}
- (void)connectionDidFinishLoading:(NSURLConnection*)theConnection {
[connection release];
connection=nil;
_imageView.image = [UIImage imageWithData:data];
if (_activityIndicator) {
[_activityIndicator stopAnimating];
}
[data release]; data=nil;
}
- (UIImage*) image {
UIImageView* iv = [[self subviews] objectAtIndex:0];
return [iv image];
}
In ViewController Class Which loads image
- (UITableViewCell *)tableView:(UITableView *)tV cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *reuseIdentifier =#"CellIdentifier";
ListCell *cell = (ListCell *)[tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
if (cell==nil) {
cell = [[ListCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier];
NSMutableDictionary *dicResult = [arrResults objectAtIndex:indexPath.row];
NSURL *url=[NSURL URLWithString:[dicResult objectForKey:#"Image"]];
AsynchronousImageView *asyncImageView = [[AsynchronousImageView alloc] initWithFrame:CGRectMake(5, 10,80,80)];
[asyncImageView loadImageFromURL:url
defaultImageName:#"DefaultImage.png"
showDefaultImage:NO
showActivityIndicator:YES
activityIndicatorRect:CGRectMake(5, 10,30,30)
activityIndicatorStyle:UIActivityIndicatorViewStyleGray]; // load our image with URL asynchronously
[cell.contentView addSubview:asyncImageView];
// cell.imgLocationView.image = [UIImage imageNamed:[dicResult valueForKey:#"Image"]];
[asyncImageView release];
}
if([arrResults count]==1)
{
UITableViewCell *cell1=[tableView dequeueReusableCellWithIdentifier:reuseIdentifier];
if(cell1==nil)
cell1=[[[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier] autorelease];
NSMutableDictionary *dicResult = [arrResults objectAtIndex:0];
cell1.textLabel.text=[dicResult valueForKey:#"NoResults"];
return cell1;
}
else
{
NSMutableDictionary *dicResult = [arrResults objectAtIndex:indexPath.row];
NSString *title = [NSString stringWithFormat:#"%# Bedrooms-%#", [dicResult valueForKey:KEY_NUMBER_OF_BEDROOMS],[dicResult valueForKey:KEY_PROPERTY_TYPE]];
NSString *strAddress = [dicResult valueForKey:KEY_DISPLAY_NAME];
NSString *address = [strAddress stringByReplacingOccurrencesOfString:#", " withString:#"\n"];
NSString *price = [dicResult valueForKey:KEY_PRICE];
NSString *distance = [dicResult valueForKey:KEY_DISTANCE];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.lblTitle.text = title;
cell.lblAddress.text = address;
if ([price length]>0) {
cell.lblPrice.text = [NSString stringWithFormat:#"£%#",price];
}else{
cell.lblPrice.text = #"";
}
if ([distance length]>0) {
cell.lblmiles.text = [NSString stringWithFormat:#"%.2f miles",[distance floatValue]];
}else{
cell.lblmiles.text = #"";
}
}
return cell;
}
How can i resolve this?
I have attached heapshot analysis screen shot of it.Here non Object consumes so much of memory what is that?
this is the error:
NSString *reuseIdentifier = [NSString stringWithFormat:#"%d",indexPath.row];
it seems you are NOT reusing cells, but creating a new cell for every row of your table!!!
this way if you need to see 100 or 1000 rows, you create/allocate 100 or 1000 object cells.
that's not the right use of a UITableView.
the "magic" of UITableView is that it reuse cells, and it just creates and allocates just the minor number of cells needed...
e.g. consider you have a vertical spaces of 480 pixels for your tables, and your cells are 100 pixel height, then you just need 5 cells for time, no need to create 1000 cells, you can see just 5 cells at time...
so the magic is to reuse an already allocated cell when you scroll it up and it goes out of screen, and to reset it's contents (images and text) and to use it for the new call that user is going to see down...
While cell reuse is not the problem, leaking cells is:
cell = [[ListCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:reuseIdentifier];
You forgot to autorelease this, so you're leaking cells very quickly. You did remember to autorelease cell1.
I'm newbie to objective-c . My problem is slow navigation between controllers.
When I jump from one controller to another it's really slow, because it gets data from the service, parse it and then display it. All this takes around 4-5 secs.
Now I've put all this data fetching in loadView of my DetailController. Is there anyway that I show the controller first and then display the data. Because as of now, when I select a row on my root controller, it shows activity and stays on root controller and then it navigates to the DetailController.
So it loads the data first and then displays it. Is there anyway that I can reverse this process, show the controller first and then display it..
this is what I'm doing
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
DataCollection *collection = [DataCollection sharedObject];
Product *tempProduct = [[collection popularProducts] objectAtIndex:row] ;
ProductDetailViewController *tempProductDetail = [[ProductDetailViewController alloc] init];
tempProductDetail.homeViewProduct = tempProduct;
[tempProductDetail release];
}
and then the Detail Controller
- (void)loadView {
[super loadView];
// here's all the data loading and parsing ... which takes time
}
//Here's the method for downloading the data, I'm using a REST service to get all the data.
- (void) getProductData:(NSString*)productId{
NSMutableString *specificURL = [[NSMutableString alloc] initWithString:#"GetProductPage?id="];
[specificURL appendFormat:#"%#",productId];
[specificURL appendFormat:#"&dataSources=&accountId=&companyId=&version=1&cultureCode=sv¤cyId=2&format=json"];
NSDictionary *serverCategories = [[NSDictionary alloc] initWithDictionary:[appRequestController proceesRequest:specificURL]];
NSArray *categories = [[[serverCategories objectForKey:#"DataSources"] objectAtIndex:0] objectForKey:#"Items"];
[serverCategories release];
if ([[productsArray objectAtIndex:j] objectForKey:#"Name"] != [NSNull null]) {
[product setProductName:[[productsArray objectAtIndex:j] objectForKey:#"Name"]];
}
else {
[product setProductName:#""];
}
if ([[productsArray objectAtIndex:j] objectForKey:#"ThumbnailImage"] != [NSNull null]) {
[product setProductImage:[[productsArray objectAtIndex:j] objectForKey:#"ThumbnailImage"]];
}
else {
[product setProductImage:#""];
}
if ([[productsArray objectAtIndex:j] objectForKey:#"Price"] != [NSNull null]) {
[product setProductPrice:[[productsArray objectAtIndex:j] objectForKey:#"Price"]];
}
else {
[product setProductPrice:#""];
}
[[collection popularProducts] addObject:product];
[product release];
}
I've copy pasted junks of the code so that you can get an idea how I'm getting the data. Below is the code for cellForRowAtIndexPath:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
NSString *MyIdentifier = #"mainMenuIdentifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:MyIdentifier] autorelease];
[cell setSelectedBackgroundView:[[UIImageView alloc] initWithImage:[UIImage imageNamed:#"highlightstrip.png"]]];
}
UILabel *productNameLbl = [[UILabel alloc] initWithFrame:CGRectMake(90, 8, 200,10)];
[productNameLbl setText:[NSString stringWithString:[[[[DataCollection sharedObject] popularProducts]objectAtIndex:indexPath.row] productName]]];
[productNameLbl setFont:[UIFont boldSystemFontOfSize:12.0]];
[cell addSubview:productNameLbl];
[productNameLbl release];
UILabel *productSubLbl = [[UILabel alloc] initWithFrame:CGRectMake(90, 22, 190,40)];
[productSubLbl setText:[NSString stringWithString:[[[[DataCollection sharedObject] popularProducts]objectAtIndex:indexPath.row] productSubHeader]]];
[productSubLbl setTextColor:[UIColor colorWithRed:102.0/255.0 green:102.0/255.0 blue:102/255.0 alpha:1.0]];
productSubLbl.lineBreakMode = UILineBreakModeWordWrap;
productSubLbl.numberOfLines = 3;
[productSubLbl setFont:[UIFont fontWithName:#"Arial" size:10]];
[cell addSubview:productSubLbl];
[productSubLbl release];
NSMutableString *url = [[NSMutableString alloc] initWithString:#""];
NSString *baseImageURL = #"http://samplesite.com/";
NSString *imagePath = [NSString stringWithString:[[[[DataCollection sharedObject] popularProducts]objectAtIndex:indexPath.row] productImage]];
[url appendString:baseImageURL];
[url appendString:imagePath];
NSURL *imageURL = [NSURL URLWithString:url];
NSData *data = [NSData dataWithContentsOfURL:imageURL];
UIImage *img = [[UIImage alloc] initWithData:data cache:NO];
UIImageView *imageView = [[UIImageView alloc] initWithImage:img];
[imageView setFrame:CGRectMake(10, 10, 70, 70)];
[cell addSubview:imageView];
[imageView release];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
return cell;
}
Pass the url or product id to the detail view. Inside the viewDidLoad of the ProductDetail, create a background thread to retrieve your data. When you background thread finish, it will notify your ProductDetail controller with the detail. At that time, you update your ProductDetailView.
To get your data on a background thread you can use
NSThread
NSOperation
Pass the row into your ProductDetailViewController, and do the fetch in it's viewDidLoad method. You might also want to show a spinner (UIActivityIndicator), and you definitely should read up on background threads.
To make view available to user and loading data after some time you can use NSTimer.
Just have a NSTimer method which will be called in viewDidLoad method as
[NSTimer scheduledTimerWithTimeInterval:<#(NSTimeInterval)ti#> target:<#(id)aTarget#> selector:<#(SEL)aSelector#> userInfo:<#(id)userInfo#> repeats:<#(BOOL)yesOrNo#>]
put some time interval which will be calling your loading method after that.
Hope this helps.
Kindly look the below code..i found from web
- (void) loadData {
NSOperationQueue *queue = [NSOperationQueue new];
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
selector:#selector(loadDataWithOperation) object:nil];
[queue addOperation:operation];
[operation release];
}
- (void) loadDataWithOperation {
NSURL *dataURL = [NSURL URLWithString:#"http://icodeblog.com/samples/nsoperation/data.plist"];
NSArray *tmp_array = [NSArray arrayWithContentsOfURL:dataURL];
for(NSString *str in tmp_array) {
[self.array addObject:str];
}
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:YES];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = #"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];
}
[cell.textLabel setText:[self.array objectAtIndex:indexPath.row]];
return cell;
}
I have found the above code which loads the text from the web without any problem.( i mean gui is not hanging ).like wise i want to load images into the table view.for that i have wriiten the below code.
- (void) loadDataWithOperation {
NSString *Img_id, *Img_name, *DynamicImgUrl;
Img_id = xmltag.equip_id;
Img_name = xmltag.image;
DynamicImgUrl = [NSString stringWithFormat:#"http://test.com/pics/equipment/%#/%#",Img_id, Img_name];
NSURL *ImageUrl = [NSURL URLWithString:DynamicImgUrl];
//UIImage *image = [UIImage imageWithData: [NSData dataWithContentsOfURL:ImageUrl]];
NSArray *tmp_array = [NSArray arrayWithContentsOfURL:ImageUrl];
for(NSString *str in tmp_array) {
[self.array addObject:str];
}
[self.tableView performSelectorOnMainThread:#selector(reloadData) withObject:nil waitUntilDone:YES];
}
Am i correct here? how can i go with adding into the table view...
Kindly help me out...
U are seeking to display images on Tableview from URL, when u download image from url we need to consider some issues, it will freeze ur table view if u goahead with ordinary loading image.
i suggest u look at LazyLoading image
u see this link http://developer.apple.com/library/ios/#samplecode/LazyTableImages/Introduction/Intro.html
Download this sample and get to know the things
im having an issue with my tableviews and threaded data. in my app im downloading data in xml, including an image url which im then parsing to get the image for my tableviews.
im using the asyncimageview class (markj.net). if i thread the download of my xml, then realoadData the cells appear fine , appart form the images never load, until they are dequeued and reloaded, and i have no idea how to fix it.
help please.....
ok i managed to fix it using this
- (void)loadImagesForVisibleCells;
{
NSArray cells = [self.newsStories visibleCells];
[cells retain];
for (int i = 0; i < [cells count]; i++)
{
// Go through each cell in the array and call its loadContent method if it responds to it.
CGRect frame;
frame.size.width=75; frame.size.height=75;
frame.origin.x=0; frame.origin.y=0;
AsyncImageView asyncImage = [[[AsyncImageView alloc]
initWithFrame:frame] autorelease];
//AsyncImageView *asyncImage = (AsyncImageView *)[[cells objectAtIndex: i] retain];
//*flickrCell = (FlickrCell *)[[cells objectAtIndex: i] retain];
NewcastleStories *aStory = [stories objectAtIndex:i];
//AsyncImageView* asyncImage = [[AsyncImageView alloc]init];
[asyncImage emptyImages];
NSURL* url =[NSURL URLWithString:aStory.picture];
[asyncImage loadImageFromURL:url];
//[cells objectAtIndex:i])
static NSString *CellIdentifier = #"ImageCell";
UITableViewCell *cell = [newsStories dequeueReusableCellWithIdentifier:CellIdentifier];
[cell.contentView addSubview:asyncImage];
[[self newsStories]setNeedsLayout];
[[self newsStories] setNeedsDisplay];
//[self dataLoaded];
//[asyncImage loadImage];
//[flickrCell release];
//flickrCell = nil;
}
[cells release];
}
but it has to be called in the main thread using
[self performSelectorOnMainThread:#selector(loadImagesForVisibleCells) withObject:nil waitUntilDone:YES];