UITableViewCell 中自定义 UIView 的 drawRect 的错误行为
Wrong behavior of drawRect of custom UIView inside UITableViewCell
我制作的自定义 UIView(名为 EventBadge).
这是我的自定义代码 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
我有一个 UITableView 可以填充三种不同类型的 UITableViewCell,比方说 TypeA、TypeB 和 TypeC.
TypeA 和 TypeB 里面有不同的元素 (UILabels, UIViews 等等),它们都有我的 EventBadge。 TypeC 仅由标准元素构成。
这是所有细胞类型的代码:
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
我选择 cellForRowAtIndexPath 方法中的单元格类型 ViewController 查看存储在 _tableviewData(用于填充 tableView 的数组)中的对象类型。代码如下所示:
- (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];
}
}
在每个方法 createTypeXCell 中,我直接处理元素并设置它们的属性。一切都按预期工作 除了在我的自定义视图上设置的 属性。所以 TypeC 完美地工作并且 TypeA 和 TypeB 中的一切都按预期工作 except 我的 eventBadgeView.
上的颜色和图标设置
我得到的行为是每个 eventBadgeView,无论 UITableViewCell 属于哪个,都使用最后一个的属性进行绘制eventBadgeView 正在处理(数组的最后一项)。
如果我稍微向上或向下滚动 UITableView,足以呈现一个项目,该项目就会得到很好的更新,具有我之前设置的属性。
但是如果我滚动太多,一切都会再次变得混乱。
我注意到关于 setNeedsDisplay,drawRect 总是在很晚之后被调用,并且我了解到这本来就是这样的。
我已经阅读了很多 SO 帖子(我没有在这里链接所有这些帖子)并且基于这些我尝试做的事情(没有运气)是:
- 在方法中调用 [cell.eventBadgeView setNeedsDisplay]
设置属性后创建单元格
- 将设置单元格属性和[cell.eventBadgeView setNeedsDisplay]的所有部分放在dispatch_async
中
- 使用CALayer来"force" drawRect同步执行
也许因为我是 ObjectiveC 的新手,所以我遗漏了一些基本的东西,而且我对我的自定义 EventBadge : UIView 有很大的怀疑 class 因为其他一切都正常。
在此先感谢您的帮助! :)
您应该在实现主体之外声明这些变量,否则它们将像 .m 文件中的全局变量一样受到威胁(有关此内容的更多信息 here)
UIColor *badgeFillColor;
UIColor *badgeBorderColor;
MyCustomIcons badgeIcon;
将它们放在接口中(在 .m 文件中或直接在 .h 中)并将它们声明为 @属性
@interface MPEventBadge ()
@property (strong, nonatomic) UIColor *badgeFillColor;
@property (strong, nonatomic) UIColor *badgeBorderColor;
@property (nonatomic) MPInsertEventIcons badgeIcon;
@end
然后您可以像
一样访问变量
_badgeFillColor = color;
我制作的自定义 UIView(名为 EventBadge).
这是我的自定义代码 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
我有一个 UITableView 可以填充三种不同类型的 UITableViewCell,比方说 TypeA、TypeB 和 TypeC.
TypeA 和 TypeB 里面有不同的元素 (UILabels, UIViews 等等),它们都有我的 EventBadge。 TypeC 仅由标准元素构成。
这是所有细胞类型的代码:
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
我选择 cellForRowAtIndexPath 方法中的单元格类型 ViewController 查看存储在 _tableviewData(用于填充 tableView 的数组)中的对象类型。代码如下所示:
- (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];
}
}
在每个方法 createTypeXCell 中,我直接处理元素并设置它们的属性。一切都按预期工作 除了在我的自定义视图上设置的 属性。所以 TypeC 完美地工作并且 TypeA 和 TypeB 中的一切都按预期工作 except 我的 eventBadgeView.
上的颜色和图标设置我得到的行为是每个 eventBadgeView,无论 UITableViewCell 属于哪个,都使用最后一个的属性进行绘制eventBadgeView 正在处理(数组的最后一项)。
如果我稍微向上或向下滚动 UITableView,足以呈现一个项目,该项目就会得到很好的更新,具有我之前设置的属性。 但是如果我滚动太多,一切都会再次变得混乱。
我注意到关于 setNeedsDisplay,drawRect 总是在很晚之后被调用,并且我了解到这本来就是这样的。
我已经阅读了很多 SO 帖子(我没有在这里链接所有这些帖子)并且基于这些我尝试做的事情(没有运气)是:
- 在方法中调用 [cell.eventBadgeView setNeedsDisplay] 设置属性后创建单元格
- 将设置单元格属性和[cell.eventBadgeView setNeedsDisplay]的所有部分放在dispatch_async 中
- 使用CALayer来"force" drawRect同步执行
也许因为我是 ObjectiveC 的新手,所以我遗漏了一些基本的东西,而且我对我的自定义 EventBadge : UIView 有很大的怀疑 class 因为其他一切都正常。
在此先感谢您的帮助! :)
您应该在实现主体之外声明这些变量,否则它们将像 .m 文件中的全局变量一样受到威胁(有关此内容的更多信息 here)
UIColor *badgeFillColor;
UIColor *badgeBorderColor;
MyCustomIcons badgeIcon;
将它们放在接口中(在 .m 文件中或直接在 .h 中)并将它们声明为 @属性
@interface MPEventBadge ()
@property (strong, nonatomic) UIColor *badgeFillColor;
@property (strong, nonatomic) UIColor *badgeBorderColor;
@property (nonatomic) MPInsertEventIcons badgeIcon;
@end
然后您可以像
一样访问变量_badgeFillColor = color;