views:

3635

answers:

5

I have a custom view in a custom table cell. Every time a specific property on the custom view is changed I call [self setNeedsDisplay] which redraws the view in - (void)drawRect:(CGRect)rect. That property is set in the table view delegate's tableView:cellForRowAtIndexPath:. But when the table view is larger than the screen and cells have to be reused, drawRect isn't called every time setNeedsDisplay is. Especially when I flick the table quickly . Slow scrolling works fine. This leads to the information in the first and last cells often being incorrect.

In the log I can see that normally, for every call to setNeedsDisplay there is a call to drawRect, but when I scroll the table view quickly there are more calls to setNeedsDisplay than drawRect. Surely there should be a one-to-one ratio here.

I use the same custom view in a normal UIView, and it redraws perfectly every time I call setNeedsDisplay. The problem seems isolated to table views and reusing cells.

Does anyone know what's going on here? Is there any other way to force the custom view to redraw itself?

A: 

You should be calling [_table_ reloadData] to redraw the table. Not [_table_cell_ setNeedsDsiplay]

Kailoa Kadano
I'm not redrawing the table. Just the custom view.
Kare Morstol
reloadData calls setNeedsDisplay for you at at the right time. You seem to be fighting the framework. Let the framework decide which ones to update and just implement tableView:cellForRowAtIndexPath:
Kailoa Kadano
reloadData causes all the cells for the table to be destroyed, the datasource to be requeried, and the visible cells to be recreated. That is a lot more than is necessary to just refresh a single cell.
rpetrich
A: 

It sounds like there are two circumstances under which the view is refreshed: if the internal value changes (and you call [self setNeedsDisplay] to force an update), and also if the tableview is requesting a cell be drawn (or reused). You're not explicitly redrawing that custom view in that situation.

According to the docs:

The table view'€™s delegate in tableView:cellForRowAtIndexPath: should always reset all content when reusing a cell.

If tableview is recycling cells, it'll hand back a cell with the old values still in place. It's up to you to make sure the content is updated. Depending on how you've set up your custom table cell, you might have to do more to make sure the view is uptodate but the simplest thing would be in the custom table cell handler to force a call to [myCustomView setNeedsDisplay].

Ramin
That's what I'm doing. In tableView:cellForRowAtIndexPath: I change the underlying data for the custom view and call setNeedsDisplay on it. In the log I can see that normally, for every call to setNeedsDisplay there is a call to drawRect, but when I scroll the table view quickly there are more calls to setNeedsDisplay than drawRect. Surely there should be a one-to-one ratio here.
Kare Morstol
Ramin
+2  A: 

setNeedsDisplay will only call drawRect: if the view is visible, otherwise it invalidates the view's layer and drawRect: will be called when it does become visible.

When scrolling through a table view, the table view will request cells in the hopes of adding them to the view. If scrolling fast enough, some cells will take too long to prepare and by the time they are ready the location they should be positioned is offscreen. In these cases, the table view discards them instead of adding them to the scroll view (and thus drawRect: will not be called)

rpetrich
Also, setNeedsDisplay only affects the view it is called on. If necessary, any subviews will have to be invalidated independently
rpetrich
Sounds plausible for large tables, but my table has only 10 rows, 8 of which are visible at the same time. Wouldn't this only apply if a cell travelled from below the table to above it before it could be redrawn?
Kare Morstol
Yeah, that would only apply for large tables. In your table all 8 visible rows should receive drawRect:... if not that's probably a bug somewhere. Can you post your code (or a simplified version that still has the bug)?
rpetrich
It turns out that even when every call to setNeedsDisplay results in a call to drawRect, the content of the custom view is still often wrong. And when scrolling normally the custom view that is about to appear will often be redrawn in a cell that is already visible at the other end of the table! Which makes absolutely no sense. This video (http://nottoobadsoftware.com/Repetitions/drawrect_bug.mov) explains it better. Pay attention to the second cell from the bottom. Each row has a label at the top and a custom view below it (the last text in the custom view is just for debugging purposes).
Kare Morstol
Sometimes other cells are redrawn like this.BTW: when the actual problem has changed as much as it has here, should I just rewrite the question or create a new question? I mean the title isn't even accurate any more. As you can see from my score I'm new here.
Kare Morstol
Normally I would say accept the answer that answers the original problem and create a new question, but I'm not sure that applies as the original problem wasn't solved.
rpetrich
Also, you mentioned you are adding a custom view to each cell: generally I've found creating a custom UITableViewCell subclass that manages it's own drawing to be both easier to debug and to perform much better than adding a bunch of subviews to the standard cell.
rpetrich
I should probably have mentioned I am using a UITableViewCell subclass with a UILabel and a custom view. The reason I'm using a custom view inside the custom cell is that the custom view is also used elsewhere. I will see if I can extract just the necessary code, but I can't post all of it in a question can I? There must be some limit to how much code you can post here, though I couldn't find anything about it in the FAQ.
Kare Morstol
You could probably post a scaled-down version of it (paint a single color specified by an ivar+property or something similar)
rpetrich
A: 

I'm having the a very similar problem - I have a cell that draws all its contents inside a custom drawRect. Every time cellForRow... is called i call [cell setNeedsDisplay] and sometimes when I scroll quickly, the cell redrew seems to get called late - I will often see the cell for half a second or so before it refreshes the display.

Is there anyway to force redraw immediately rather than waiting for the run loop to realize it needs doing? Sounds like this would solve both our problems (which may indeed be the same).

+3  A: 

I think I struggled with this issue for almost a week before I found the problem. Here it is, and I kid you not:

The pointer to this custom view was defined in the .m file instead of the .h file, making it a class variable instead of an instance variable.

And yes I am very, very embarrassed.

Kare Morstol