An efficient way of redrawing a UITableViewCell once its data is available - iphone

I currently have one UITableViewController that contains many cells with simple data in it. When the UITableViewController instantiates a UITableViewCell, it begins running a background thread for each individual cell to get its state from the server. The UITableViewCell is subclassed and already has a UIImageView property that needs to get added to the contentView once the data from the server is ready.
My problem is that is that I need to call [tableView reloadData] everytime I get new data back from the background threads. This is a bit overkill, as I can simply add the UIImageView to the contentView by accessing the affected cell directly. I'm just not sure of the best way to find the cell for when my data management utility is done doing its work (speaking to the server).
Does it make sense to do something such as passing the indexPath of the cell when calling my data manager to run its background task and then passing that indexPath back when the task is done? Is that overkill?
What's your favorite way for handling this common task?
Thanks SO crew.

In the case where the data needs to change inside of the cells I do not use the dequeueReusableCellWithIdentifier:(NSString *)identifier method. That stops the UITableView from caching the cell. When table view cells scroll off of the screen they are released. There is a delegate method,
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
that gets called just before your cell will be drawn. You can use this to, yes pass the indexPath to the manager.
When your data comes back, I usually call the method
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
to get the cell, then set the text in the cell and call its UIView superclass method,
- (void)setNeedsLayout.
That will tell the cell to redraw.
Another way I have done it is to use a custom cell, and have that custom cell subclass call the manager for its data directly when it is drawn. When the data comes back, it calls a custom delegate method on itself to update its internal UILabel objects and then calls setNeedsLayout.

If you don't mind having a placeholder in your cell while the image loads, you can use something like TTImageView in the Three20 library. The image view is always in the cell and you specify its image URL. The image view handles the URL request, and when the image is loaded, it's automatically displayed.
While I don't use Three20, I do something very similar in my code. This is compatible with table cell reuse identifiers -- when a cell scrolls off the screen, its image view URL is changed to a new value, which cancels the URL request (if it's still ongoing) and starts a new one. Combined with caching, when you scroll back up the image will load instantaneously.

Related

How to implement "Loading" in UITableView when scrolling down?

I am working on iPhone application . This application contains plenty of web services. I need to show list in UITableView with page by page. How to show "Loading" and call web service when scrolling down the UITableview ? Please help me..
I assume that you are adding a custom cell to the bottom of your table view to indicate the end of the current data set. When you create that cell, set its tag property to a meaningful constant you've previously defined, like END_OF_TABLE_CELL.
Then use the delegate method tableView:willDisplayCell:forRowAtIndexPath to check if the cell about to display is your custom cell, and trigger a reload of the table's datasource.
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
{
//check your cell's tag
if (cell.tag == END_OF_TABLE_CELL) {
//Call your custom method to fetch more data and reload the table
// You should also remove this custom cell from the table and add one at the bottom
}
}
This way you will have a table that refreshes itself whenever a user scrolls down far enough to bring the custom cell into view.
Good Luck!

temporarily prevent a UITableViewCell from being recycled

My UITableViewCells can accept input of data. While working in one cell, it would be perfectly natural for the user to want to scroll up, check something in another cell, then return to the first cell and continue the data entry.
The problem is that this will frequently cause the UITableView to recycle the cell, which wreaks havoc in my program.
Is there any way to temporarily tell iOS not to recycle the cell?
Once the data entry is done, I am fine if it is recycled.
I should add that the data entry uses a custom keyboard. So first responder status is not an issue.
Give a different cellIdentifier to the cells that are significantly different. If the cell at the bottom has its own identifier, then when the user scrolls to the bottom, it won't recycle your cell from the top. However, this will still preserve your cell at the top in the reuse queue, so that when you scroll back to the top, you won't need to recreate it.
There's no way how to prevent cell recycling if you do use dequeueReusableCellWithIdentifier: method of UITableView in a common way.
If you would like to do this, you should implement it on your own in UITableView's data source protocol method - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath.
As Chiefly Izzy said, it's not a common way to work. And going against the way the lists work may cause problems. If you want to reduce your problem, keep the content of your cell in memory and use this saved content to refill the cell when willDisplayCell is called instead of rebuilding the whole thing from start each time.
Gendolkari's solution gave me an idea for temporarily removing a cell from the dequeue cycle. It does involve setting a read-only value using KVC, so it's not for the truly hack-adverse.
When you first want to remove the cell from the cycle, change the reuseIdentifier for that particular cell. We want to be able to find the cell later so rename it something that we can index against. I found [NSIndexPath description] to be useful here. The reuseIdentifier is a read-only property, but this is Objective-C, and we have KVC:
[cell setValue:[[self.tableView indexPathForCell:cell] description] forKey:#"reuseIdentifier"];
Now whenever we reuse a cell in the cellForRowAtIndexPath:
UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:#"cellIdentifier"];
we won't get back this particular cell because we have changed the reuseIdentifier.
However, if we scroll down then scroll back up, we probably want to display the original cell that we removed from the dequeue cycle. To do this when we are reusing a cell first attempt to dequeue a cell based on indexPath, and if we can't find that attempt to dequeue a cell simply based on the basic cell identifier string:
UITableViewCell *cell;
cell = [self.tableView dequeueReusableCellWithIdentifier:[indexPath description]];
if (!cell) cell = [self.tableView dequeueReusableCellWithIdentifier:#"cellIdentifier"];
When you want to put that cell back into the dequeue cycle, simply change the reuseIdentifier back to the original value:
[cell setValue:#"cellIdentifier" forKey:#"reuseIdentifier"];

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

Decrease UITableViewCell width and put custom button on the left

Is there any way to decrease the standard width of grouped UITableViewCell and put a custom button on the left side(outside of cell boundary)? I tried to change the cell size but it keeps same
You are going to have to fake the editing mode.
What I mean by that is that as AtomRiot said you have to subclass UITableViewCell so that when in editing mode you show the button you want on the left, outside the cell.
But first things first.
To change the indentation level for your cells all you need to do is implement this delegate method for the UITableView
- (NSInteger)tableView:(UITableView *)tableView indentationLevelForRowAtIndexPath:(NSIndexPath *)indexPath
So that takes care of it. Then in your UITableViewCell subclass all I would do is to implement the method
- (void)setEditing:(BOOL)editing animated:(BOOL)animated {
which I assume is called when the table the cell belongs to has changed to editing mode.
There I would fade in (or animate in any way you want) a button to appear on the left of your cell.
I have done it inside a grouped-style cell but never on the outside.
Give it a try!
You could subclass UITableCell and add your own custom views inside of it. I have not personally added a button inside one but it should work. It may get confused with the row selected call the tableview makes if you are implementing that.
The Cocoanetics blog seems to have a pretty good solution to this:
http://www.cocoanetics.com/2010/03/how-to-shrink-cells/

Drawing selected state on custom UITableViewCell

I have a customer UITableViewCell whose whole display is drawn indrawRect. When it draws it creates CGLayers so that it can reuse certain pieces when something is changed.
I have changed my UITableViewCellSelectionStyle to "None" because I don't want the default selected view to cover my drawing.
My problem is that I call setNeedsDisplay in setSelected:animated: for my cell but by the time drawRect is called, setSelected:animated: has already been called again to deselect the cell. In my table view controller didSelectRowAtIndexPath, I call deselectRowAtIndexPath as Apple advises.
EDIT - I have also tried called the setNeedsDisplay on my cell from my table view controller's (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath but that didn't change anything.
Any ideas? Thanks.
Use the table cell's selectedBackgroundView property. If you assign a custom view to that, it'll get shown and hidden at the same time as the default selection backgrounds—in other words, without having to wait for the setNeedsDisplay to get around to calling drawRect: on the cell itself.