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

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).

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.

UITableView cells with both UIActivityIndicator & UIProgressView

I have a UITableView that contains several cells and some of them (the ones for files that are still uploading) have both an UIActivityIndicator and an UIProgressView. The ones for files that are finished use a different icon (instead of the activity indicator) and hide the progressview.
This table is using a NSFetchedResultsController as data source, so I get the updates on the data model and update the content.
Everything works just fine. The problem, however, is performance. Every time I call reloadData my UIActivityIndicators flicker, and it's not very smooth. Although I'm caching from the nib file, reloadData will have to calculate the new progress % for the ProgressView and I don't do anything with the ActivityIndicator other than hiding it if upload is complete.
Anybody ever tried something similar? Is there a workaround?
I was thinking about having an array of my progressview references and use that instead of calling reloadData.. not sure if this is the correct approach.
Thanks,
Fernando
When you call reloadData on the UITableView, all the cells of the table view are completely refreshed, re-assembled and redrawn. All the old ones are thrown away. This means that all the subviews of the UITableViewCells are removed and re-created too (including your UIActivityIndicator and UIProgressView). The refresh causes these views to flicker, or perhaps jump back to their start state. As there is no way of setting the frame of a UIActivityIndicator, your suggestion of restoring some progress value simply isn't possible.
Instead, perhaps you should try and engineer your "refresh" to not require a complete refresh of the table? For example, if you want to change the text of a UITextField within the view, you could simply access this text field and set the text property (no refresh is required). Or, if you want to hide your progress indicators, you could go into the appropriate object instances and set their property. You should design your app so that this is possible. Making changes this way avoids having to reload cells from scratch.
Besides the benefit of fixing your problem, using this method of updating, you should also see a large performance increase. reloadData is a very costly method to use and should be only used if it is absolutely necessary to really re-create the entire UITableView from scratch.
Hope this helps. :)

Threaded thumbnail-loading for imageview in a table cell

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 :)

How expensive is UITableView's reloadData?

I'm curious just how expensive in as far as resources go is UITableView's reloadData? I have an app which will make roughly 10 subsequent HTTP requests, and as it gets data / preps, it reloads the tableView. As the data set grows larger and larger, it's becoming very sluggish. I'm trying to figure out if it's because of the amount of times I'm reloading the tableView or because of how I'm grabbing/parsing the data.
What's the best practice in this case?
From UITableView.h:
- (void)reloadData; // reloads everything from scratch. redisplays visible rows. because we only keep info about visible rows, this is cheap. will adjust offset if table shrinks
"This is cheap."
implement your table view methods well and it'll be no big deal to call this function all the time.
On a side note, you should try to use the appropriate methods to animate adding and removing rows if you are thinking of using reloadData for that.
The best practice is to have your implementation of cellForRowAtIndexPath: do as little work as possible. In fact, it really shouldn't be doing any work except populating the UITableViewCell instance with the data it needs to display.
You should be using cached UITableViewCells so you don't have to allocate a new cell each time. If you can do your parsing and such in a separate thread and make the parsed data, ready to present, accessible to cellForRowAtIndexPath:, you shouldn't have any performance problems.
You didn't say if you were using a custom UITableViewCell subclass, but if you are, deep view hierarchies can also present a performance problem, since each view in the hierarchy gets drawn. The flatter you can make UITableViewCells, the better.
Hope that gets you moving in the right direction.
Best thing to do is profile your app to see where it is slow.
That said, if your table cells are all the same height, then I think
reloadData
only has to call
cellForRowAtIndexPath
for cells that are visible on screen.
Table view reload expense is:
Figuring out how many sections and
rows per sections you have
getting row heights.
Row heights in particular are figured out for all elements of the table, anytime you call reload data.
The remaining expense is cellForRowAtIndexPath, which is usually not too bad because it only is called for as many rows as are on the screen. It can be bad when scrolling if you do not reuse cells like you are supposed to.
The key for you is probably, to ask yourself what triggers the HTML load and possibly move that into a background thread.
Boot To The Head is correct.
I'm doing a progressive one-by-one article list update in Instapaper, and I call -reloadData on each completed download. Sounds similar to what you're doing. It doesn't result in any noticeable performance slowdowns.