Threaded thumbnail-loading for imageview in a table cell - iphone

I'm looking for suggestions on how to make an efficient thumbnail-loader for navigation-lists in the form of tables. I can start a thread and download and cache the thumbnails, but I'm unsure on how to update a table cell with the image (a cell either visible or outside the view).
I think the app Blocket does what I'm after, if I remember correctly.
I'd like to set a placeholder image, and as the thread loads each image, it updates the cells. The placeholder could be an animated activity indicator (spinner), if possible.
Are there any terse example-code out there, or can you give suggestions on how to communicate the update to the cell and force it to display the image immediately?

Interesting problem.
I guess at some level you'll need to replace the existing cells in the table view via the reloadRowsAtIndexPaths:withRowAnimation: UITableView method, so I suspect you'll need to keep a lookup of cell contents -> indexPaths so you know which cells to update when the relevant asset becomes available.
However, you'll need to avoid breaking the existing cell re-use system. (i.e.: there's no value in pre-creating all of the cells in advance.) As such, much of this work should be done within the - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath UITableViewDelegate method, I suspect.
That said, hopefully you'll get a better response than these somewhat vague ramblings. :-)

Made the nicest threaded thumbnail loading I've seen yet on iPhone :D The hardest part was cancelling a thread when navigating away from its table. An NSThread loading images and performSelectorOnMainThread waitUntilDone:YES for when updating the cell.imageView, passing objects (cell, tableView, indexPath etc) in an array to bypass the limitation of passing only one object in withObject. Looks royale with a quick fade-in of each image as they load :)

Related

cellForRowAtIndexPath to load 3 cells above and below the visible cells

I know that cellForRowAtIndexPath only loads visible cells. Is there a way to force it so that it loads the 3 cells below and above it?
No, cellForRowAtIndexPath only loads the current cell.
Your question suggests that you're doing something that is computationally expensive or slow in your cellForRowAtIndexPath. For example, you might be doing lazy loading of images, but also want to "prefetch" some of images you need for candidate "next" cells in order to diminish the user's experience of the lazy loading. Generally, though, you wouldn't actually prefetch the UITableViewCell objects, themselves, but rather just the pieces of data that those cells need.
You might need to give us more information about what sort of stuff you feel the need to prefetch, and we can provide better counsel. It's a non-trivial issue, somewhat contingent upon having a well-designed model that your controller uses when presenting the view. You might want to share a little about your model and the nature of the stuff that you want to make sure is on-hand for the previous three and next three cells.
UITableViews usually take care themselves of the whole process of deciding when to load specific cells. They automatically remove invisible cells from it, sometimes storing those cells in an internal reusability queue.
UITableView provides a mechanism you can use to speed up the cell creation process by retrieving pre-alloc'd cells when available. If you use this method properly you should have no trouble with the scrolling speed in your table views. To do so, you need to configure the reusabilityIdentifier for a cell on creation, and call the method -[UITableView dequeueReusableCellWithIdentifier:] when you need a new cell in your cellForRowAtIndexPath: implementation.
It should be feasible for you to have your own queue of reusable and preconfigured cells if you still need more speed - although the advantages you can get from implementing something like this remain to be seen (you'd have more rows ready to use, but would also slow down cellForRowAtIndexPath: for the cell being requested). You would also need to be very careful not to clash with UITableView's standard queue.
Check out Apple's UITableView reference for more info on the reusability mechanism.

UITableView's cells release when not visible

I have a UITableView. each row is heavy object with videos, images etc.
When user scrolls this view how can I release the memory of not visible rows and load the current visible rows?
I assume you're talking about releasing memory that's used by the images and videos of your row, and not the row itself.
In your tableview delegate,
-(void) scrollViewDidEndDecelerating:(UIScrollView *)scrollView
tells you when the tableview scrolling has stopped.
[myTableView indexPathsForVisibleRows]
gives you an array of what is visible.
If your row is not in this array, it is not visible, and you can do your image/video cleanup on it.
Are you recycling UITableViewCells as per Apple's recommendations? If not, you MUST read Apple's docs. Inside the cellForRowAtIndexPath: delegate you should have something [customCell setMediaObjects:]. Inside your customCell class you can release all the previous mediaObjects from memory.
As others have said, you should make sure you are recycling cells properly, and not destroying things you would need to recreate anyway when the cell is reused.
But, you may want to release other assets that the cell or its views are retaining. Or if that cell has any pending download requests, for example, you may want to reset their priority or even cancel them when the cell is offscreen.
I think the cleanest way to do this is to just override -[UITableViewCell prepareForReuse]
This is called when the cell is put back into the reuse queue. If the user is moving up and down the table quickly, you may not want to clean the cell up the moment the cell is off the screen (by looking at indexPathsForVisibleRows, for example).
But when the cell is actually put back in the reuse queue, that is a good time to do that work since you know that cell won't appear again on screen until you dequeue and configure it again.
A Closer Look at Table-View Cells - Apple Documentation
When you call dequeueReusableCellWithIdentifier first 10 (just ten cells can be shown at screen at one moment) cells will be created, then they will be just loaded and configured for showing.
So, cells are not released.

Stop thread in UITableViewCell before it is released

I have this kind of problem where I want to download some images that will be used as thumbnails for each cell.
In order to achieve this behavior, there is a thread working in each UITableViewCell that have the role of downloading and saving the image.
But I want to stop the thread from working if the cell is not visible anymore.
Is there some delegate methods for UITableViewCell like viewWillDisappear that will help me solve this issue?
If I am thinking wrong, Is there any other way that will help me achieve this same behavior?
Thank you for your help.
UITableViewCell is only for rendering something to the screen. The controller that is controlling your tableview should be the one downloading the thumbnails and reload its data once it has the thumbnails.
A tableview only has 1 cell, it copies it in order to display the multiple rows.
Use the delegate method "willDisplayCell" to set the data of your custom tableview cell.
Due the reusable cell mechanism a single instante of UITableViewCell can be reused for display different data so UITableViewCell is not meant to be used for this logic but only for presentation purpose.
I suggest you to rethink your app logic

how to dispose cells in UITableView when they are not needed?

I am creating a very long table with images and some text data. I am using resuable cells.
I am getting high memory allocations as I scroll the table down, may be due to new images loads and add to resuable cells.
I want to know is there a way to dispose the resuable cells when they are not in the view.
Please help!
You have no way to remove the reusable cells as this task is efficiently managed by the table view itself. According to me, but I need to see some code snippet to confirm, you're not disposing the images properly.
A typical case is when you load images using the -(UIImage *)imageNamed: method and in such case all images are stored in a OS managed cache which is not as efficient as manual alloc/release.
Another case is when you create the cell and forget to autorelease it. This means that in the tableView:cellForRowAtIndexPath: method when you instantiate the new UITableViewCell you must autorelease it before leaving the method (it will be retained by UITableView internal queue).

When is the right time to change the datasource for a UITableViewController

After rendering a UITableView with a given datasource, I want to be able to switch to a different one prior to calling reloadData.
Tried doing it in the method
- (void)viewDidAppear:(BOOL)animated;
but as far as I can see this isn't called when all the visible cells are rendered.
By placing a breakpoint it seems as if nothing is actually rendered. So I must have misunderstood its actual purpose.
Is there a API callback for that or do I have to programmatically look for it?
e.g. in
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath;
when indexPath holds the index for the last rendered cell in the tableview.
To put it into perspective (somewhat).
Suppose we have a UITableView which has a "static" datasource. Once all the static data is displayed, you want to switch to a dynamic datasource which fetches the data off the internet and reloads each row as fetched.
So I'm actually looking for the "right hook" in which to make that change from one datasource to the other.
Are there any other issues one should be concerned about?
Further clarification. I have written all the required code (e.g. an UITableViewController with the static data initialized, different datasources, background threads for the dynamic views, a delegate to notify the UITableViewController as new data is coming in) what I haven't figured out (what the question is all about) is when to actually make the switch from one datasource to the next.
When you call reloadData it forces your UITableView to re-query your data source regardless of the amount of data that is already loaded. Also, the cells that are not visible are not being loaded, so practically the last cell is only filled with data if and when you scroll down to it. If you know for sure that all your cells are visible at once, as you might have a small number of them, you might do as you suggested and call your update function when indexPath holds the index for the last cell in the tableview.
Edit:
In the light of your edits, I'm thinking that perhaps the moment you should switch data sources depends on the state of your "live" data - when you've got enough data in it to be able to show enough of it on the screen. That is, let the static data be displayed, and only switch when you've already fetched a number of values in your live data source.
Another idea might be keeping only one mutable data source, with pre-loaded static initial data. As you fetch new "live" values, you start by replacing the ones that you already have, and then continue adding new values if their number exceeds the initial number of static entries.
viewDidAppear is all about the view controller, not the table. The table reloading will never call that method.
What you want to do, is better handled either via a delegate approach or using notifications. Set up the static data set in viewDidLoad, but then right away start the background dynamic data fetch. When that fetch is complete, have the code downloading the new data call you either as a delegate, or issue a notification your the view controller with the table listens to, and reset the table view data - then you can simply call reloadData, and the cellForRow:AtIndexPath: method will be called again for the rows on the screen (reload data will also revert back to the top of the table).
UITableView load the cells lazy, in other means it only loads the cells that are visible to the user. When the user then scrolls down, the table view will load the new cells while the "old" cells will be released. There are ways to load the entire data source, but this is not recommended from Apple - since it will be heavy on the memory load of the iPhone.
viewDidAppear() is probably your best bet.
The viewWillAppear: and viewDidAppear: methods are only called when your UIViewController gets pushed on the screen (e.g. by an UINavigationContoller or UITableViewController).
What I would do in your situation is to call a method on your own view controller when your dynamic data source is ready (or whenever you intend to change the data source). In this method set the data source and then call [[self tableView] reloadData];
What you should look out for is, if you are constructing your dynamic data source in the background (which I suppose you do) then you need to call your method on the main thread using performSelectorOnMainThread: