问题
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
- User scrolls down; 8 more rows are drawn
- User scrolls back to the top; the scorebar for Grace Market/Maple Pizza (row 2) has changed.
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) {
...
回答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)
来源:https://stackoverflow.com/questions/25478319/why-is-drawrect-leaving-parts-of-image