2

Here's my case:

I have a table which contains a list of restaurants, each entry shows the results of inspections over time on a scorebar that is drawn using drawRect.

When the user scrolls the table down and then up, so that scorebars with yellow and red squares are shown, previous scorebars pick up those squares. Dates are also drawn into previous scorebars.

My problem lies in getting rid if the old squares. Shouldn't they be erased each time drawRect is called?

I've placed three images below that hopefully explain what I mean.

I don't think this is a problem with table rows caching custom cells? Here the scorebars are drawn once the UITableCell establishment cell is dequeued; all the other views in the cell are correct, which makes me think the issue is with drawRect failing to clear the graphics context.

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  if (indexPath.row < self.establishments.count) {
      return [self establishmentCellForIndexPath:indexPath];
  } else if (self._noResultsFound) {
      return [self noResultsCell];
  } else {
      return [self loadingCell];
  }
}

- (UITableViewCell *)establishmentCellForIndexPath:(NSIndexPath *)indexPath {

  static NSString *CellIdentifier = @"EstablishmentCell";

  DSFEstablishment *establishment = [self.establishments objectAtIndex:[indexPath row]];

  DSFEstablishmentCell *cell = [self.tableView dequeueReusableCellWithIdentifier:CellIdentifier];

  [cell setEstablishment:establishment];
  [cell updateCellContent];

  return cell;
}

During updateCellContent, we try to clear out old scorebarviews, if they exist, but the code never finds additional subviews of the DSFScorebarView class. Finally, the scorebarView is created and added to the view's subviews.

- (void)updateCellContent {
NSLog(@"updateCellContent: %@", self.establishment.latestName);

self.name.text = self.establishment.latestName;
self.address.text = self.establishment.address;
self.type.text = self.establishment.latestType;
if (self.establishment.distance) {
    self.distance.text = [NSString stringWithFormat:@"%.2f km", self.establishment.distance];
} else {
    self.distance.text = @"";
}

// Clear out old scorebarView if exists
for (UIView *view in self.subviews) {
    if ([view isKindOfClass:[DSFScorebarView class]]) {
        NSLog(@">>>removeFromSuperview");
        [view removeFromSuperview];
    }
}

DSFScorebarView *scorebarView = [[DSFScorebarView alloc] initWithInspections:self.establishment.inspections];
[self addSubview:scorebarView];

}

The scorebarview is drawn using drawRect within initWithInspections and ensures that scorebars are no more than 17 squares long (most recent inspections). Each inspection is given a square of green = low|yellow = moderate|red = high and the year is drawn below the square.

I've set self.clearsContextBeforeDrawing = YES, but it doesn't correct the problem.

// Custom init method
- (id)initWithInspections:(NSArray *)inspections {
CGRect scorebarFrame = CGRectMake(20, 38, 273, 22);
if ((self = [super initWithFrame:scorebarFrame])) {
    self.inspections = inspections;

    self.clearsContextBeforeDrawing = YES;

    [self setBackgroundColor:[UIColor clearColor]];
}
return self;
}

- (void)drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();

float xOffset = 0; // Keep track of position of next box -- start at 1
float yOffset = 0; // Fixed Y offset. Adjust to match shadows
NSString *previousYear = nil;
UIFont *yearFont = [UIFont fontWithName:@"PFTempestaFiveCompressed" size:8.0];

// take subset/slice of inspections. only the last 17, so it will fit on screen.
int inspections_count = (int)[self.inspections count];

int startIndex = inspections_count > 17 ? inspections_count - 17 - 1 : 0;
int subarrayLength = inspections_count > 17 ? 17 : inspections_count;
NSArray *inspectionsSlice = [self.inspections subarrayWithRange:NSMakeRange(startIndex, subarrayLength)];

for (id inspection in inspectionsSlice) {

    // Top of box
    NSMutableArray *pos0RGBA = [inspection colorForStatusAtPositionRGBA:0];
    CGFloat topColor[] = ...
    CGContextSetFillColor(ctx, topColor);
    CGRect box_top = CGRectMake(xOffset, yOffset, kScoreBoxWidth, kScoreBoxHeight / 2);
    CGContextAddRect(ctx, box_top);
    CGContextFillPath(ctx);

    // Bottom of box
    NSMutableArray *pos1RGBA = [inspection colorForStatusAtPositionRGBA:1];
    CGFloat bottomColor[] = ...
    CGContextSetFillColor(ctx, bottomColor);
    CGRect box_bottom = CGRectMake(xOffset, kScoreBoxHeight / 2 + yOffset, kScoreBoxWidth, kScoreBoxHeight / 2);
    CGContextAddRect(ctx, box_bottom);
    CGContextFillPath(ctx);

    // Year Text
    NSString *theYear = [inspection dateYear];
    if (![theYear isEqualToString:previousYear]) {
        CGFloat *yearColor = ...
        CGContextSetFillColor(ctx, yearColor);
        // +/- 1/2 adjustments are positioning tweaks
        CGRect yearRect = CGRectMake(xOffset+1, yOffset+kScoreBoxHeight-2, kScoreBoxWidth+50, kScoreBoxHeight);
        [theYear drawInRect:yearRect withFont:yearFont];
        previousYear = theYear;
    }

    xOffset += kScoreBoxWidth + kScoreBoxGap;
}
  • Table loads, 4 rows are drawn

Starting position

  • User scrolls down; 8 more rows are drawn

enter image description here

  • User scrolls back to the top; the scorebar for Grace Market/Maple Pizza (row 2) has changed.

enter image description here

After implementing @HaIR's solution below, I get a white strip against the grey background, after tapping a table row. It's pretty ugly, after adding the extra width to 'white-out' the Year line.

- (void)drawRect:(CGRect)rect {
  ...
  CGFloat backgroundColour[] = {1.0, 1.0, 1.0, 1.0};
  CGContextSetFillColor(ctx, backgroundColour);
  CGRect fullBox = CGRectMake(xOffset, yOffset, kScoreBoxWidth * 17, 2 * (kScoreBoxHeight-2));
  CGContextAddRect(ctx, fullBox);
  CGContextFillPath(ctx);

  for (id inspection in inspectionsSlice) {
  ...

grey tablerow with scorebar graph and white strip

dfdumaresq
  • 1,618
  • 4
  • 17
  • 22

1 Answers1

1

You've taken over DrawRect, so its only going to do what you manually do in the dra

Fill in the entire bar area with background color before you draw anything on top of it.

You are re-using cells, so, for example, you have the Turf Hotel Restaurant graph showing underneath the 2nd version of the Grace Market/Maple Plaza.

CGContextSetFillColor(ctx, yourBackgroundColor);
CGRect fullBox = CGRectMake(xOffset, yOffset, kScoreBoxWidth * 17, kScoreBoxHeight);
CGContextAddRect(ctx, fullBox);
CGContextFillPath(ctx);

If you are just clearing the background in your cells instead of having a background color. then:

CGContextClearRect(context, rect);

at the start of your DrawRect may be more appropriate.

REVISION:

Looking at your question more closely, the root problem is that you are not finding and deleting the old DSFScoreboardView's. Perhaps you should try a recursive search through the subviews. Like:

- (void)logViewHierarchy {
    NSLog(@"%@", self);
    for (UIView *subview in self.subviews) {
        //look right here for your DSFScorebarView's
        [subview logViewHierarchy];
    }
}
@end

// In your implementation
[myView logViewHierarchy];

(taken from https://stackoverflow.com/a/2746508/793607)

Community
  • 1
  • 1
HalR
  • 11,411
  • 5
  • 48
  • 80
  • Your explanation was cut off at the end, @HaIR. But that does tell me what I suspected. Your solution works; however, it leaves a streak of background colour when the user taps the cell row. (Background colour is white, and on tap the cell turn grey). I need to avoid that. – dfdumaresq Aug 25 '14 at 04:59
  • Unfortunately, clearing the background in the cells doesn't remove the graph underneath. – dfdumaresq Aug 25 '14 at 16:12
  • I hate to run you around, but have your tried using [UIColor clearColor].CGColor for your background color? i.e. CGContextSetFillColor(ctx, [UIColor clearColor].CGColor); – HalR Aug 25 '14 at 16:16
  • Glad to run anywhere at this point! I am initializing with [self setBackgroundColor:[UIColor clearColor]]; but I also tried your suggestion: CGFloat backgroundColour[] = {0, 0, 0, 0}; CGContextSetFillColor(ctx, backgroundColour); and get the same result. Thanks, though. – dfdumaresq Aug 25 '14 at 16:29
  • 1
    You're the man, @HalR! The recursive clue was exactly what I needed, it helped identify a second scorebarview and to remove it. Thanks so much! – dfdumaresq Aug 25 '14 at 22:28