UITableViewCell 中自定义 UIView 的 drawRect 行为错误

Posted

技术标签:

【中文标题】UITableViewCell 中自定义 UIView 的 drawRect 行为错误【英文标题】:Wrong behavior of drawRect of custom UIView inside UITableViewCell 【发布时间】:2016-02-02 16:11:19 【问题描述】:

我遇到了一个有点复杂(至少在我看来是这样)的问题,我制作了一个自定义 UIView(称为 EventBadge)。

这是我的自定义类的代码:

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 填充,比如说 TypeATypeB > 和 TypeC

TypeATypeB 内部有不同的元素(UILabelsUIViews 等),它们都有我的 EventBadgeTypeC 仅由标准元素组成。

这是所有细胞类型的代码:

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

我在 ViewController 的 cellForRowAtIndexPath 方法中选择单元格类型,查看存储在 _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 完美运行,TypeATypeB 中的所有内容都按预期工作除了我的颜色和图标设置eventBadgeView

我得到的行为是每个 eventBadgeView,无论属于哪个 UITableViewCell,都会使用最后一个 eventBadgeView 的属性进行绘制正在工作(数组的最后一项)。

如果我向上或向下滚动一点 UITableView,足以呈现一个项目,该项目会得到很好的更新,具有我之前设置的属性。 但如果我滚动太多,一切都会再次混乱。

我注意到,关于 setNeedsDisplay,drawRect 总是在很久以后才被调用,我了解到这就是这样的。

我已经阅读了很多 SO 帖子(我没有在此处链接所有帖子),并且基于这些我尝试做的事情(没有运气)是:

    在方法里面调用[cell.eventBadgeView setNeedsDisplay] 设置属性后创建单元格 将设置单元格属性和[cell.eventBadgeView setNeedsDisplay]的所有部分放在dispatch_async中 使用 CALayer 来“强制”drawRect 同步执行

也许因为我是 ObjectiveC 的新手,所以我缺少一些基本的东西,而且我对我的自定义 EventBadge 有很大的怀疑: UIView 类,因为其他一切都很好。

提前感谢您的帮助! :)

【问题讨论】:

【参考方案1】:

您应该在实现主体中声明这些变量,否则它们将像 .m 文件中的全局变量一样受到威胁(有关此here 的更多信息)

UIColor *badgeFillColor;
UIColor *badgeBorderColor;
MyCustomIcons badgeIcon;

将它们放在一个接口中(在.m 文件中或直接在.h 中)并将它们声明为@property

@interface MPEventBadge ()

@property (strong, nonatomic) UIColor *badgeFillColor;
@property (strong, nonatomic) UIColor *badgeBorderColor;
@property (nonatomic) MPInsertEventIcons badgeIcon;

@end

然后你可以像

一样访问变量
_badgeFillColor = color;

【讨论】:

这让我很开心!效果很好!

以上是关于UITableViewCell 中自定义 UIView 的 drawRect 行为错误的主要内容,如果未能解决你的问题,请参考以下文章

UITableViewCell 中自定义 UIView 的 drawRect 行为错误

如何在 xamarin ios 中自定义 UITableViewCell 的背景颜色?

将 UIViewController 设置为 UITableViewCell 中自定义 UIView 的委托

iOS8中自定义UITableViewCell动态高度的内容有不同的宽度

UITableViewCell 子类,错误的高度直到滚动

UITableView 中自定义单元格的 UITapGestureRecognizer