Matt Fellows Matt Fellows - 1 month ago 7x
iOS Question

UITableViewCell reuse with custom UIView as a component causing "caching" issues

I'm creating a view which comprises of a UITableView with customised cells - I'm overriding

in my custom view. I've tried overriding UITableViewCell and adding my custom view as an IBOutlet, I've tried not overriding it and just referring to it by
[[cell subviews] objectAtIndex:0];
both yield the same results.

When I first look at the view, all is fine. If I scroll slowly, all is fine. As soon as I scroll quickly the reused cells are clearly not re-drawing because I end up with the custom drawing, being wrong for the particular cells.

The cell configuration method...

- (UITableViewCell*) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
UITableViewCell* cell = [tableView dequeueReusableCellWithIdentifier:@"DialogCell"];
MaskedRoundedCornerDIalogCell* dialogCell = (MaskedRoundedCornerDIalogCell*)[[[cell contentView] subviews] objectAtIndex:0];
[dialogCell setPadding:10];
if (indexPath.row % 2 == 0) {
[dialogCell setAlignLeft:YES];
[dialogCell setMaskTopLeftOnly];
[[dialogCell textContent] setText:@"LEFT ALIGNED TEXT"];
[[dialogCell textContent] setTextAlignment:NSTextAlignmentLeft];
} else {
[dialogCell setAlignLeft:NO];
[dialogCell setMaskBottomRightOnly];
[[dialogCell textContent] setText:@"RIGHT ALIGNED TEXT"];
[[dialogCell textContent] setTextAlignment:NSTextAlignmentRight];
return cell;

My Custom drawing code in my MaskedRoundedCornerDIalogCell implementation (A class which extends UIView and is added to the UITableViewCell):

- (void) drawRect:(CGRect)rect {

int maxWidth = rect.size.width - 50;
CGRect container;
if (_alignLeft) {
container = CGRectMake(rect.origin.x + _padding, rect.origin.y + _padding, maxWidth - (2* _padding), rect.size.height - (2*_padding));
} else {
container = CGRectMake((rect.size.width - _padding) - (maxWidth - (2* _padding)), rect.origin.y + _padding, maxWidth - (2* _padding), rect.size.height - (2*_padding));

UIRectCorner roundedCorners;
if (!_maskTopLeft) {
roundedCorners = roundedCorners | UIRectCornerTopLeft;
if (!_maskTopRight) {
roundedCorners = roundedCorners | UIRectCornerTopRight;
if (!_maskBottomLeft) {
roundedCorners = roundedCorners | UIRectCornerBottomLeft;
if (!_maskBottomRight) {
roundedCorners = roundedCorners | UIRectCornerBottomRight;
UIBezierPath* containerBezierPath = [UIBezierPath bezierPathWithRoundedRect:container byRoundingCorners: roundedCorners cornerRadii:CGSizeMake(25.0F, 25.0F)];

[[UIColor lightGrayColor] setFill];
[containerBezierPath fillWithBlendMode: kCGBlendModeNormal alpha:1.0f];

The way it looks when I first launch it:
Correct Layout

The way it looks after scrolling a few times:
enter image description here

Any advice, gratefully received...


I'm not sure if you're overriding drawRect in your UITableViewCell subclass or if it's in a custom UIView class. My suggestion would be to do your drawing in a custom UIView class and then add that view as a subview of your cell - just in case UITableViewCell is doing something in drawRect that you're accidentally overriding.

In any case, the reason you're seeing this behaviour is because drawRect is only called when the view first comes on the screen or if it's invalidated. From the docs:

This method is called when a view is first displayed or when an event occurs that invalidates a visible part of the view. You should never call this method directly yourself. To invalidate part of your view, and thus cause that portion to be redrawn, call the setNeedsDisplay or setNeedsDisplayInRect: method instead.

In your cell's setMask.. methods, try calling [self.customDrawingView setNeedsDisplay] to invalidate the drawing and force an update.

You can use this method or the setNeedsDisplayInRect: to notify the system that your view’s contents need to be redrawn. This method makes a note of the request and returns immediately. The view is not actually redrawn until the next drawing cycle, at which point all invalidated views are updated.

You should use this method to request that a view be redrawn only when the content or appearance of the view change. If you simply change the geometry of the view, the view is typically not redrawn. Instead, its existing content is adjusted based on the value in the view’s contentMode property. Redisplaying the existing content improves performance by avoiding the need to redraw content that has not changed.