Advertise Mobile SDKs Books Events Forum News Social Networking Support Us
Follow @iphonedevsdk on Twitter

Interface 2, Advanced iOS
Mockup & Code Gen
($9.99)

Make your own iPhone apps
and run them live!
(free)

Pic Frame Dynamo: Photo Editing
($0.99)

Abiliator
($1.99)

Want your application or service advertised on iPhone Dev SDK?

Go Back   iPhone Dev SDK Forum > iPhone SDK Development Forums > iPhone SDK Development

Reply
 
LinkBack Thread Tools Display Modes
Old 07-31-2008, 12:38 AM   #1 (permalink)
I can't think without pen
 
Join Date: Jul 2008
Location: New York
Posts: 72
aahmed753 is on a distinguished road
Default UIImage loading for each table row

I am loading an image for each table rows. Following code is inside cellForRowAtIndexPath function.

Code:
// consider 'data' is object model
// this code is outside if(cell == nil) check
// code is also in inside custom UITableCell

UIImageView *imgView = (UIImageView *) [self viewWithTag:IMAGE_TAG];	
NSData *receivedData = [NSData dataWithContentsOfURL:[NSURL URLWithString:data.imageLink]];
UIImage *image = [[UIImage alloc] initWithData:receivedData cache:NO];
imgView.image = image;
i can't release non of these variables since no one else is retaining it.

the scroll is not smooth at all. how can i optimize it?

Last edited by aahmed753; 07-31-2008 at 12:41 AM.
aahmed753 is offline   Reply With Quote
Old 07-31-2008, 02:52 AM   #2 (permalink)
New Member
 
Join Date: Apr 2008
Location: Germany
Posts: 154
ChriB is an unknown quantity at this point
Default

As you mentioned:
This code is not inside
Code:
if (cell == nil) {}
This seems to be your problem. You are creating a new UIImageView and a new image for every cell.

Try doing this:
Code:
	if (cell == nil) {
		cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:MyIdentifier] autorelease];
		UIImageView *imgView = (UIImageView *)[self viewWithTag:IMAGE_TAG];
		NSData *receivedData = [NSData dataWithContentsOfURL:[NSURL URLWithString:data.imageLink]];
		UIImage *image = [[UIImage alloc] initWithData:receivedData cache:NO];
		imgView.image = image;
		[image release];
		[receivedData release];
	}
You can now release the image and receivedData because you passed them to the methods, now after setting the image of imgView there is no need for the image or receivedData objects.
I don't understand why you are creating a new imageView everytime a new cell is created. Don't you miss adding it to the cell as a subView or something like that?
ChriB is offline   Reply With Quote
Old 07-31-2008, 03:04 AM   #3 (permalink)
I can't think without pen
 
Join Date: Jul 2008
Location: New York
Posts: 72
aahmed753 is on a distinguished road
Default

so how would you change the image on every cell? because table view will reuse the image initially created with the cell...
aahmed753 is offline   Reply With Quote
Old 07-31-2008, 03:19 AM   #4 (permalink)
I can't think without pen
 
Join Date: Jul 2008
Location: New York
Posts: 72
aahmed753 is on a distinguished road
Default

sorry, my question was misleading... i need to add different images on each cell, not the same image.
aahmed753 is offline   Reply With Quote
Old 07-31-2008, 03:24 AM   #5 (permalink)
New Member
 
Join Date: Apr 2008
Location: Germany
Posts: 154
ChriB is an unknown quantity at this point
Default

Quote:
Originally Posted by aahmed753 View Post
so how would you change the image on every cell? because table view will reuse the image initially created with the cell...
Ok, here's how I would do that:
Create an array of all the different URLs for the different cells.
Create your default cell in the if (cell == nil) part.
Right after that part you configure it. So try to get the URL for the row (indexPath.row) of your URL-Array.
cell.image = theImageCreatedUsingTheRightURL;

Code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
	static NSString *MyIdentifier = @"MyIdentifier";
	UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
	NSArray *urlArray = [[NSArray alloc] initWithObjects:@"testUrlOne", @"testUrlTwo", nil];
	if (cell == nil) {
		cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:MyIdentifier] autorelease];
	}
	// Configure the cell
	NSData *receivedData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[urlArray objectAtIndex:indexPath.row]]];
	UIImage *image = [[UIImage alloc] initWithData:receivedData];
	cell.image = image;
	[receivedData release];
	[image release];
	return cell;
}

Last edited by ChriB; 07-31-2008 at 03:30 AM.
ChriB is offline   Reply With Quote
Old 07-31-2008, 03:38 AM   #6 (permalink)
I can't think without pen
 
Join Date: Jul 2008
Location: New York
Posts: 72
aahmed753 is on a distinguished road
Default

basically that's what i am doing... but it seems like for every scroll table is re-reading data from url and then creating image. i checked and found that reading the data from internet is most sluggish part.

only another way i found to do is, load all images before loading the table. then get the image (instead of url) for each cell. That works well for the scrolling, but my concern is 1) loading take pretty long 2) why load all these content (table may have 20-50 rows) if user does not need to scroll down?

any comment? thanks..
aahmed753 is offline   Reply With Quote
Old 07-31-2008, 03:45 AM   #7 (permalink)
New Member
 
Join Date: Apr 2008
Location: Germany
Posts: 154
ChriB is an unknown quantity at this point
Default

So you need a way to load the image when it's the first time it is showed but after that it shouldn't load the image again...
The best way I can think of is, as you mentioned, loading all images before it begins showing them.

Idea:
Code:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
	static NSString *MyIdentifier = @"MyIdentifier";
	UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:MyIdentifier];
	NSArray *urlArray = [[NSArray alloc] initWithObjects:@"testUrlOne", @"testUrlTwo", nil];
	if (cell == nil) {
		cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:MyIdentifier] autorelease];
	}
	// Configure the cell
	if (cell.image == nil) {
		NSData *receivedData = [NSData dataWithContentsOfURL:[NSURL URLWithString:[urlArray objectAtIndex:indexPath.row]]];
		UIImage *image = [[UIImage alloc] initWithData:receivedData];
		cell.image = image;
		[receivedData release];
		[image release];
	}
	return cell;
}
Shouldn't that work?
ChriB is offline   Reply With Quote
Old 07-31-2008, 04:13 AM   #8 (permalink)
I can't think without pen
 
Join Date: Jul 2008
Location: New York
Posts: 72
aahmed753 is on a distinguished road
Default

hmm, that didn't work... it's reusing same image for most of the cells... maybe i need to create a matrix for rows with images and url values...

damn, finished four packs of red bull with this issue already! Thanks for sticky up bro.

I wonder how Apple did this in their App store application and iTunes application. It is so smooth, and if you notice, you will see text data loads the entire table initially, but images loads on demand for only current visible rows... neat. i want it!!!!!
aahmed753 is offline   Reply With Quote
Old 07-31-2008, 04:15 AM   #9 (permalink)
New Member
 
Join Date: Apr 2008
Location: Germany
Posts: 154
ChriB is an unknown quantity at this point
Default

Yeah it's like "load while beeing in background".. Great that I never want to do something like that, but keep us updated if you find something, or let's hope there's someone else who has some ideas
Isn't that possible via a seperate thread? (I don't know that just something that went into my head..)
ChriB is offline   Reply With Quote
Old 07-31-2008, 05:12 AM   #10 (permalink)
Lost in a sea of code
 
BostonMerlin's Avatar
 
Join Date: Apr 2008
Location: Boston
Posts: 399
BostonMerlin is an unknown quantity at this point
Default

i would say threading is the key as well. main thread loads up the cells text.. once complete runs down all the images. i believe they only ever show 25 or so at a time until you click the 'show more' link.
__________________
----------------------------------------------------------------------
I love being a dad, flying airplanes and writing code.
----------------------------------------------------------------------
Follow me on Twitter: @BostonMerlin
Feed your brain on Twitter: @iPhoneDev101
----------------------------------------------------------------------
iPhone Apps:
BostonMerlin is offline   Reply With Quote
Old 07-31-2008, 11:32 AM   #11 (permalink)
I can't think without pen
 
Join Date: Jul 2008
Location: New York
Posts: 72
aahmed753 is on a distinguished road
Default

I tried threading, didn't see any result. If you see apple's App Store, they don't do "Show More" link... all hundreds of rows are drawn at once... still you have smooth scrolling. I am dying to know how they do it, specially how images are only loaded for the cells that visible!!!
aahmed753 is offline   Reply With Quote
Old 07-31-2008, 01:45 PM   #12 (permalink)
I can't think without pen
 
Join Date: Jul 2008
Location: New York
Posts: 72
aahmed753 is on a distinguished road
Default

Ok guys, I just found the solution!!
after reading the documentation, i found that there is delegate method called:
– tableView:willLoadVisibleCellsInRowAtIndexPaths:
This method along with visibleCells property will do the trick to only load images for the cells that is currently visible!!

HOWEVER... It seems like codes inside this method is not executing. I tried using NSLog() to check. Also, I can't find this method in my iMac's XCode's documentation, but I can find it in MacBook Air's installation of XCode! wired. both has iPhone SDK 2.0 installed.... any idea?

Is this delegate method part of SDK 2.1? but my Air's Xcode documentation is part of 2.0 and declared in UITableView.h file.....
aahmed753 is offline   Reply With Quote
Old 08-12-2008, 11:23 AM   #13 (permalink)
Registered Member
 
Join Date: Jul 2008
Posts: 34
victor_zzm is on a distinguished road
Default

Hi,

Did you get the solution? I am also looking for this. How can we use threading? I mean, if we use one thread to dowload the images, if one image is ready can we notify the table?
victor_zzm is offline   Reply With Quote
Old 08-12-2008, 11:31 AM   #14 (permalink)
I can't think without pen
 
Join Date: Jul 2008
Location: New York
Posts: 72
aahmed753 is on a distinguished road
Default

yes, you can notify the tableviewcontroller once one image is downloaded. alternatively, you can also put each image download to NSOperation or NSInvocationOperation. google with these two keywords, you will know what to do.
aahmed753 is offline   Reply With Quote
Old 08-12-2008, 09:43 PM   #15 (permalink)
Registered Member
 
Join Date: Jul 2008
Posts: 34
victor_zzm is on a distinguished road
Default

Thanks for your reply. Do you have willLoadVisibleCellsInRowAtIndexPaths working?
victor_zzm is offline   Reply With Quote
Old 08-13-2008, 08:18 PM   #16 (permalink)
Registered Member
 
Join Date: Jul 2008
Posts: 34
victor_zzm is on a distinguished road
Default

Hi aahmed753,

Could you please explain a bit how to notify tableViewController, event/message? What API should I use?
Thanks a lot,
Victor
victor_zzm is offline   Reply With Quote
Old 08-13-2008, 08:29 PM   #17 (permalink)
I can't think without pen
 
Join Date: Jul 2008
Location: New York
Posts: 72
aahmed753 is on a distinguished road
Default

There are many ways to do that. Preferred way to do would be to define a delegate in your application with method name that will be called after download is finished, say:
Code:
-(void)downloadDidFinish:(id)data;
your tableview controller need to conforms to the delegate which in turn will basically call tableview reload method each time delegate method is called.

now, when you are calling the thread (NSOperation or NSInvocationOperation, or any standard NSThread), you pass the delegate object in the parameter, which is self variable. so after thread task is completed, the thread will call:
Code:
 [delegate performSelectorOnMainThread:@selector(downloadDidFinish:) withObject:data];
hope this helps. requires little bit advanced design concept, but coding is not hard.

Last edited by aahmed753; 08-13-2008 at 08:34 PM. Reason: renamed the method to downloadDidFinish
aahmed753 is offline   Reply With Quote
Old 08-14-2008, 03:42 AM   #18 (permalink)
Registered Member
 
Join Date: Jul 2008
Posts: 34
victor_zzm is on a distinguished road
Default

Hi, thanks a lot for your help.
Another question:
Do you have willLoadVisibleCellsInRowAtIndexPaths working?
I could not find the function in the documentation, I think we can use tableView:willDisplayCell:forRowAtIndexPath.
victor_zzm is offline   Reply With Quote
Old 08-14-2008, 05:12 AM   #19 (permalink)
New Member
 
Join Date: Aug 2008
Posts: 3
neckaros is on a distinguished road
Default

Quote:
Originally Posted by aahmed753 View Post
There are many ways to do that. Preferred way to do would be to define a delegate in your application with method name that will be called after download is finished, say:
Code:
-(void)downloadDidFinish:(id)data;
your tableview controller need to conforms to the delegate which in turn will basically call tableview reload method each time delegate method is called.

now, when you are calling the thread (NSOperation or NSInvocationOperation, or any standard NSThread), you pass the delegate object in the parameter, which is self variable. so after thread task is completed, the thread will call:
Code:
 [delegate performSelectorOnMainThread:@selector(downloadDidFinish:) withObject:data];
hope this helps. requires little bit advanced design concept, but coding is not hard.
I'm not sure of everything that we need to do. does it need "willLoadVisibleCellsInRowAtIndexPaths" because it is nowhere to be seen.

You launch the new thread in the Cell creation passing the cell object and you load the image for cell.image in this new thread?

Can you please post your code as a tutorial? Thanks in advance

Last edited by neckaros; 08-14-2008 at 05:26 AM.
neckaros is offline   Reply With Quote
Old 08-14-2008, 08:18 AM   #20 (permalink)
Registered Member
 
Join Date: Jul 2008
Posts: 34
victor_zzm is on a distinguished road
Default

The following is what I want to do, could you guys help to review it, thanks a lot.

I will define the following members in the TableViewController class:
NSMutableArray imageArray ;
NSMutableArray urlArray;
UIActivityIndicatorView activityIndicator;

1.

cellForRowAtIndexPath
{

[activityIndicator startAnimating];
cell.image = imageArray[imageIndex]; //initial state for image will be [UIImage imageNamed: @”blank.png”];
}
2. willLoadVisibleCellsInRowAtIndexPaths
{
[self performSelectorInBackground:@selector(getImageData ) withObject:imageIndex];

}
3. didFinishDownload:
{
[self.tableView reloadData];
[activityIndicator stopAnimating]
}

4. getImageData
{
// a. get url from urlArray by imageIndex
// b. get imageData using CGImageCreateWithJPEGDataProvider or NSURLConnection to get the imageData
// c. Update imageArray
[self performSelectorOnMainThread:@selector(didFinishDow nload: ) withObject:data];
}

PS: 1) I did not use a seperated delegate
2) Need to make sure willLoadVisibleCellsInRowAtIndexPaths is working
victor_zzm is offline   Reply With Quote
Old 08-14-2008, 08:56 AM   #21 (permalink)
I can't think without pen
 
Join Date: Jul 2008
Location: New York
Posts: 72
aahmed753 is on a distinguished road
Default

few points:
1) performing background selector in the same controller instance is bit scary. This will put the controller into multi threaded mode. you just have to be little extra careful what variables you use and might need to put @syncronize block or have locking machanism.

2) I confirmed with Apple engineer, that willLoadVisibleCellsInRowAtIndexPaths does not exists any more. It used to in beta version, but has been take out in the final. I asked him way, he does not know. Instead you can use visibleCells property to determine which cells are visible at given point

Instead you can follow this pattern:
1) at viewDidLoad, you start that network activity indicator or the activity indicator view, and send request to the background thread for ALL image downloads (not only for the visible rows)
2) at cellForIndexPath you intialize the image with @"blank.png"
3) when background thread send didFinsihDownload message to the main thread, you perform tableView reload.

so user at first get table cells with text and empty image. as soon as image download is completed, images will be reloaded for the visible cells automatically.

hoping you don't have lots of lots of rows!!
aahmed753 is offline   Reply With Quote
Old 08-14-2008, 09:29 AM   #22 (permalink)
New Member
 
Join Date: Aug 2008
Posts: 3
neckaros is on a distinguished road
Default

I just checked the facebook app and pictures for user and other pictures only gets loaded when they become visible.

I only noticed now that the table view only load tableviewcell when they are displayed. Maybe downloading in an other thread images in an NSDictionnary with keys corresponding to rowIndex may be the solution?

thanks for your help

Last edited by neckaros; 08-14-2008 at 11:06 AM.
neckaros is offline   Reply With Quote
Old 08-14-2008, 09:34 PM   #23 (permalink)
Registered Member
 
Join Date: Jul 2008
Posts: 34
victor_zzm is on a distinguished road
Default

aahmed753,

1) I do not see the differences between delegate and method in the same controller, if we use delegate, the controller should adopt the protocol, there will be multithread too. Please correct me if I am wrong.
2) visibleCells or indexPathsForVisibleRows? I think we need call them in imageData collecting thread, but how can we know the visibleCells change so that we can trigger the thread to start download new data?
3) Unfortunately we still need visibleCells to display the visible cells when they are ready. You can have a look of Big Canvas PhotoShare at App store, Recent Photos, that is what I need.
Thanks a lot.

Last edited by victor_zzm; 08-15-2008 at 01:48 AM.
victor_zzm is offline   Reply With Quote
Old 08-15-2008, 05:08 AM   #24 (permalink)
I can't think without pen
 
Join Date: Jul 2008
Location: New York
Posts: 72
aahmed753 is on a distinguished road
Default

difference is that you are not making the controller instance as part of new separate thread. instead you start a thread on new class instance. delegate methods will be used only to call the notification method in main thread, so controller class is not part of the new thread. This info was given to me by the Apple engineer when i used a support ticket and was dealing with these issues.

visibleCells is found in table view
Code:
self.tableView.visibleCells
aahmed753 is offline   Reply With Quote
Old 09-07-2008, 11:10 PM   #25 (permalink)
Registered Member
 
Join Date: May 2008
Posts: 13
sangau001 is an unknown quantity at this point
Default can anyone pls paste some codes

hi all!
i m facing the same issue, i want to create an appliaction, that require me to load the text in the table cell first and then load images.
unable to do it properly, facing the same old smooth less scrolling issue.

If u guys have figured out some way, please do paste the code, that will be of great help for new codes like me.
sangau001 is offline   Reply With Quote
Reply

Bookmarks

Tags
uiimage, uitable, uitablecell

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off
Trackbacks are On
Pingbacks are On
Refbacks are On



» Advertisements
» Online Users: 335
9 members and 326 guests
chiataytuday, givensur, ipodphone, jbro, mer10, mtl_tech_guy, Punkjumper, vilisei, yomo710
Most users ever online was 1,387, 04-10-2012 at 04:21 AM.
» Stats
Members: 175,649
Threads: 94,113
Posts: 402,881
Top Poster: BrianSlick (7,990)
Welcome to our newest member, Anwerbl
Powered by vBadvanced CMPS v3.1.0

All times are GMT -5. The time now is 09:13 PM.
Powered by vBulletin® Version 3.8.0
Copyright ©2000 - 2012, Jelsoft Enterprises Ltd.
Search Engine Friendly URLs by vBSEO 3.3.0