UIView overriding drawRect causes view not to obey

2019-04-07 06:59发布

问题:

I am trying to override the drawRect: method of UIView in my custom view. However, my view has a border radius defined as:

    sub = [[[NSBundle mainBundle] loadNibNamed:@"ProfileView" owner:self options:nil] objectAtIndex:0];
    [self addSubview:sub];
    [sub setUserInteractionEnabled:YES];
    [self setUserInteractionEnabled:YES];
    CALayer *layer = sub.layer;
    layer.masksToBounds = YES;
    layer.borderWidth = 5.0;
    layer.borderColor = [UIColor whiteColor].CGColor;
    layer.cornerRadius = 30.0;

This works perfectly and places a nice border with a border radius around my view (don't mind the diagonal/straight white lines at the back, they have nothing to do with this view):

However, when I try to override the drawRect: method in my view, I can see a black background not masking to bounds. I don't do anything (currently), here is my code:

-(void)drawRect:(CGRect)rect{
    [super drawRect:rect];
}

And here is the result:

I've changed nothing but the draw method. How can I override the draw method while keeping my view obey the corner radius mask? Is this a bug in iOS or am I missing something?

回答1:

I don't know the full answer, but I do know that UIView's implementation of drawLayer:inContext: works differently depending on whether you have implemented drawRect: or not. Perhaps masking/clipping to bounds is one of those things it does differently.

You can try solving your issue a number of ways:

  • make your background transparent:

        layer.backgroundColor = [UIColor clearColor].CGColor;
    
  • clip yourself inside your custom drawRect::

    - (void)drawRect:(CGRect)rect {
        [[UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:30.0] addClip];
        [image drawInRect:rect];  // or whatever
    }
    
  • carve out the corners explicitly:

    CGContextBeginPath(c);
    CGContextAddArc(c, r, r, r, M_PI, 3*M_PI_2, 0);
    CGContextAddLineToPoint(c, 0, 0);
    CGContextClosePath(c);
    CGContextClip(c);
    [[UIColor grayColor] setFill];
    UIRectFill(rect);
    

I stole those last 2 suggestions from this great presentation from WWDC 2010: Advanced Performance Optimization on iPhone OS (video listed at this index page -- annoyingly, no direct link).



回答2:

This is an old question, but I was recently reading a useful blog post titled Abusing UIView. In it the author advises against overriding drawRect to do things like add a border when there are other ways of doing it. He says,

Overriding drawRect: causes a performance hit. I haven’t profiled it, so I don’t know if it’s a signifigant performance under normal circumstances (it’s probably not), but in most cases, folks who override drawRect: can accomplish what they want much easier by setting properties on a view’s layer property. If you need to draw an outline around a view, here’s how you would do it:

#import <QuartzCore/QuartzCore.h>

- (id)initWithFrame:(CGRect)frame {
  self = [super initWithFrame:frame];
  if (self) {
      self.layer.borderWidth = 2.f;
      self.layer.borderColor = [UIColor redColor].CGColor;
  }
  return self;

I realize this probably doesn't solve the problem of the black background showing through. It is just something else to think about.



回答3:

Setting

opaque = false // NO for ObjC

on the view solves the problem.



回答4:

Probably you shouldn't call the

[super drawRect:rect]

inside your drawRect. Apple says that:

If you subclass UIView directly, your implementation of this method does not need to call super. However, if you are subclassing a different view class, you should call super at some point in your implementation.

It seems that calling the super method causes strange behaviour.