I'm facing a bit complicated (at least it looks like it to me) problem with a custom UIView that I made (called EventBadge).
Here's the code of my custom class:
EventBadge.h
@interface EventBadge : UIView
- (void)setBadgeFillColor:(UIColor *) color;
- (void)setBadgeBorderColor:(UIColor *) color;
- (void)setBadgeIcon:(MyCustomIcons) icon;
@end
EventBadge.m
@implementation EventBadge
UIColor *badgeFillColor;
UIColor *badgeBorderColor;
MyCustomIcons badgeIcon;
- (void)drawRect:(CGRect)rect {
// Gets graphic context
CGContextRef context = UIGraphicsGetCurrentContext();
// Sets fill and border colors for cirlce
CGContextSetFillColor(context, CGColorGetComponents([badgeFillColor CGColor]));
CGContextSetStrokeColor(context, CGColorGetComponents([badgeBorderColor CGColor]));
// Set border line width
CGContextSetLineWidth(context, 2.0);
// Set rect containing circle as inset of rect
CGRect circle = CGRectInset(rect, 1, 1);
// Draw fill and stroke into rect
CGContextFillEllipseInRect(context, circle);
CGContextStrokeEllipseInRect(context, circle);
// Draws icon
[self drawBadgeIconInside:circle];
// Fill graphic context with path
CGContextFillPath(context);
}
/**
* Sets the background color for the badge and forces refresh
*/
- (void)setBadgeFillColor:(UIColor *) color{
badgeFillColor = color;
[self setNeedsDisplay];
}
/**
* Sets the background color for the badge and forces refresh
*/
- (void)setBadgeBorderColor:(UIColor *) color{
badgeBorderColor = color;
[self setNeedsDisplay];
}
/**
* Sets the icon for the badge and forces refresh
*/
- (void)setBadgeIcon:(MyCustomIcons) icon{
badgeIcon = icon;
[self setNeedsDisplay];
}
/**
* Draws the badge icon inside a rectangle
*/
- (void)drawBadgeIconInside:(CGRect) rect {
// Creates the inner rectangle from the original one (20x20)
CGRect iconContainer = CGRectInset(rect, 5, 5);
// Switch on badgeIcon: many different supported types
switch (badgeIcon) {
case EventLocation:
[StyleKit drawIconLocationWithFrame:iconContainer colorBase:[StyleKit blackMP]];
break;
case EventCar:
[StyleKit drawIconCarWithFrame:iconContainer colorBase:[StyleKit blackMP]];
break;
default:
MyLog(MyLogLevelError, @"INVALID MyCustomIcon");
break;
}
}
@end
I have a UITableView that can be filled with three different types of UITableViewCell, let's say TypeA, TypeB and TypeC.
TypeA and TypeB have different elements inside (UILabels, UIViews and so on) and they both have my EventBadge. TypeC is made of standard elements only.
Here's the code for all cell types:
TypeA.h
@interface TypeACell : UITableViewCell
@property (strong, nonatomic) IBOutlet UIView *prevRouteView;
@property (strong, nonatomic) IBOutlet UIView *nextRouteView;
@property (strong, nonatomic) IBOutlet UILabel *addressLabel;
@property (strong, nonatomic) IBOutlet EventBadge *eventBadgeView;
@end
TypeB.h
@interface TypeBCell : UITableViewCell
@property (strong, nonatomic) IBOutlet EventBadge *eventBadgeView;
@property (strong, nonatomic) IBOutlet UIView *prevRouteView;
@property (strong, nonatomic) IBOutlet UIView *nextRouteView;
@property (strong, nonatomic) IBOutlet UILabel *titleLabel;
@property (strong, nonatomic) IBOutlet UILabel *addressLabel;
@property (strong, nonatomic) IBOutlet UILabel *startTime;
@property (strong, nonatomic) IBOutlet UILabel *endTime;
@property (strong, nonatomic) IBOutlet CalendarColorView *calendarColor;
@end
TypeC.h
@interface TypeCCell : UITableViewCell
@property (strong, nonatomic) IBOutlet UIView *routeView;
@property (strong, nonatomic) IBOutlet UILabel *duration;
@property (strong, nonatomic) IBOutlet UILabel *startTime;
@property (strong, nonatomic) IBOutlet UILabel *endTime;
@property (strong, nonatomic) IBOutlet CalendarColorView *calendarColor;
@property (strong, nonatomic) IBOutlet TransportTypeIconView *transportTypeView;
@end
I choose the type of cell inside cellForRowAtIndexPath method of my ViewController looking at the type of object stored in _tableviewData (the array used to fill the tableView). The code looks like this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
if([_tableviewData[indexPath.row] isKindOfClass:[EventTypeA class]]){
EventTypeA *event = (EventTypeA *)_tableviewData[indexPath.row];
return [self tableView:tableView createTypeACell:event atIndexPath:indexPath];
}
else if([_tableviewData[indexPath.row] isKindOfClass:[EventTypeB class]]) {
EventTypeB *event = (EventTypeB *)_tableviewData[indexPath.row];
return [self tableView:tableView createTypeBCell:event atIndexPath:indexPath];
}
else {
EventTypeC *event = (EventTypeC *)_tableviewData[indexPath.row];
return [self tableView:tableView createTypeCCell:event atIndexPath:indexPath];
}
}
Inside each method createTypeXCell I work directly on elements and set their properties. Everything is working as expected except properties set on my custom view. So TypeC works perfectly and everything in TypeA and TypeB works as expected except the settings for colors and icons on my eventBadgeView.
The behaviour that I get is that each eventBadgeView, no matter which UITableViewCell belongs to, gets painted with the properties of the last eventBadgeView being worked (the last item of the array).
If I scroll a little bit up or down the UITableView, enough to render one item, that item gets updated well, with the properties I set previously. But if I scroll too much everything gets messed up once again.
I've noticed that drawRect gets always called a lot later with regards to setNeedsDisplay and I've learned that this is meant to be like this.
I've read on lots of SO posts (I'm not linking here all of them) and based on those what I've tried to do (with no luck) is:
- call [cell.eventBadgeView setNeedsDisplay] inside the method that creates the cell after setting properties
- put all the part of setting cell properties and [cell.eventBadgeView setNeedsDisplay] inside dispatch_async
- use a CALayer to "force" drawRect to be executed synchronously
Maybe as I'm new to ObjectiveC I'm missing some basic things, and I have big doubts on my custom EventBadge : UIView class since everything else works fine.
Thanks in advance for the help! :)