Which would be fine if Apple didn't require you to hand craft them yourself on your UI components. You might expect a "draw this with rounded corners" method. Actually, given their prevalence, you'd expect it to be a setting on UIView:
cornerRadius
or something like that.But since that doesn't exist, every developer has to write his or her own take on it. So far, mine has become fairly standard. My UIView classes implement
drawRect
to set a curvy clip region. I have a utility method that takes any rect, with a desired radius, and returns a path for a rounded rectangle. Add that to the graphics context, call CGContextClip
, and voila, I have voluptuousness. This has worked well.Then I tried to do it on a UITableView embedded in a larger view. It's a view, right? So this should work, right? Except that UITableView's
drawRect
method doesn't really do anything. The corners don't get clipped if you subclass it and clip within drawRect
.Searching on this topic suggests that this is a common problem. I read various ideas, didn't really like them for one reason or another, and then came up with my own strategy. Why not overlay my table with a view that would act as a window? It would draw the part that I normally clip off and then make the interior transparent. And with a bit of work, I could make it a reusable view I could put atop other views that didn't behave the way I wanted. No more mucking around with clipping in random subviews!
I am not a graphics programmer. Not only did this take me a while, but there are probably better ways to do it. However, you may find yourself wondering how to do the same thing, and so I'm sharing my results.
Here's my init method. I used
bgColor
to represent the color on the outside of the corners, and I set the actual backgroundColor
to clearColor
I tried using clearColor
as the fill color for my rounded path, but that didn't work. It was transparent over the view's background color, which meant the whole rectangle was filled in with the view's background, not the view underneath.
- (id)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
bgColor = [UIColor blackColor].CGColor;
self.backgroundColor = [UIColor clearColor];
self.userInteractionEnabled = YES;
}
return self;
}
And here's the drawing code. I used EOFillPath to ensure that my interior was empty. Yes, I hardcoded the radius. Yes, that's lame. I'll fix it in a refactor pass.
- (void)drawRect:(CGRect)rect {
// Drawing code
CGContextRef ctxt = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(ctxt,bgColor);
CGContextAddRect(ctxt,rect);
CGPathRef curvePath = [Utils roundedCornerPathFromRect:rect withRadius:10.0];
CGContextAddPath(ctxt, curvePath);
CGPathRelease(curvePath);
CGContextEOFillPath(ctxt);
[super drawRect: rect];
}
That's the result of a fair amount of fiddling, and it now works. Except for one problem: You've covered the view you want the user to see with another view, effectively removing the user's ability to interact with it. To get around this, I created a "coveredView" reference within RoundedCornerOverlay and set it in the view controller managing the whole view. I overrode
nextResponder
in my view to return this reference. Then I overrode the various event methods you inherit from UIView
. The final step was to realize I needed to convert incoming points to be relevant within the covered view.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[[self nextResponder] touchesBegan:touches withEvent:event];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
[[self nextResponder] touchesMoved:touches withEvent:event];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
[[self nextResponder] touchesEnded:touches withEvent:event];
}
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
[[self nextResponder] touchesCancelled:touches withEvent:event];
}
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
return [[self coveredView]
hitTest:[self convertPoint:point toView:[self coveredView]] withEvent:event];
}
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
return [[self coveredView]
pointInside:[self convertPoint:point toView:[self coveredView]] withEvent:event];
}