Thursday, November 13, 2008

Creating Transparent UIViews - a Rounded Rect View

I recently needed a view that had the shape of a rounded rectangle. Drawing a rounded rect using Core Graphics isn't particularly hard. As a matter of fact, the QuartzDemo sample code shows one way to do it. So, I dutifully created the view to draw a rounded rect. No matter what I did, however, it drew white all the way to the boundary of the view. My first version of the rounded rectangle view came out decidedly non-rounded.

The problem was that I was using the backgroundColor property inherited from UIView to identify what color to draw the rounded rectangle. But, and here's the point to remember today: if you need objects below yours to show through parts of your view without using alpha, the backgroundColor property MUST be set to [UIColor clearColor]. This is true even if you do not call drawRect on super.

The other thing you need to do is make sure that the opaqueproperty is set to NO. Ordinarily, opaque should be set to YES as an optimization, but that optimization isn't available to us if we want stuff to show through from below.

My second attempt at a rounded rectangle view was decidedly more rounded:

Because I knew that I would likely have need of a rounded rectangle view in the future, I wrote it generically. And, here it is, may it serve you well:

RoundedRectView.h
//
// RoundedRectView.h
//
// Created by Jeff LaMarche on 11/13/08.

#import <UIKit/UIKit.h>

#define kDefaultStrokeColor [UIColor whiteColor]
#define kDefaultRectColor [UIColor whiteColor]
#define kDefaultStrokeWidth 1.0
#define kDefaultCornerRadius 30.0

@interface RoundedRectView : UIView {
UIColor *strokeColor;
UIColor *rectColor;
CGFloat strokeWidth;
CGFloat cornerRadius;
}
@property (nonatomic, retain) UIColor *strokeColor;
@property (nonatomic, retain) UIColor *rectColor;
@property CGFloat strokeWidth;
@property CGFloat cornerRadius;
@end



RoundedRectView.m
//
// RoundedRectView.m
//
// Created by Jeff LaMarche on 11/13/08.

#import "RoundedRectView.h"

@implementation RoundedRectView
@synthesize strokeColor;
@synthesize rectColor;
@synthesize strokeWidth;
@synthesize cornerRadius;
- (id)initWithCoder:(NSCoder *)decoder
{
if (self = [super initWithCoder:decoder])
{
self.strokeColor = kDefaultStrokeColor;
self.backgroundColor = [UIColor clearColor];
self.strokeWidth = kDefaultStrokeWidth;
self.rectColor = kDefaultRectColor;
self.cornerRadius = kDefaultCornerRadius;
}
return self;
}
- (id)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
// Initialization code
self.opaque = NO;
self.strokeColor = kDefaultStrokeColor;
self.backgroundColor = [UIColor clearColor];
self.rectColor = kDefaultRectColor;
self.strokeWidth = kDefaultStrokeWidth;
self.cornerRadius = kDefaultCornerRadius;
}
return self;
}
- (void)setBackgroundColor:(UIColor *)newBGColor
{
// Ignore any attempt to set background color - backgroundColor must stay set to clearColor
// We could throw an exception here, but that would cause problems with IB, since backgroundColor
// is a palletized property, IB will attempt to set backgroundColor for any view that is loaded
// from a nib, so instead, we just quietly ignore this.
//
// Alternatively, we could put an NSLog statement here to tell the programmer to set rectColor...
}
- (void)setOpaque:(BOOL)newIsOpaque
{
// Ignore attempt to set opaque to YES.
}
- (void)drawRect:(CGRect)rect {

CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, strokeWidth);
CGContextSetStrokeColorWithColor(context, self.strokeColor.CGColor);
CGContextSetFillColorWithColor(context, self.rectColor.CGColor);

CGRect rrect = self.bounds;

CGFloat radius = cornerRadius;
CGFloat width = CGRectGetWidth(rrect);
CGFloat height = CGRectGetHeight(rrect);

// Make sure corner radius isn't larger than half the shorter side
if (radius > width/2.0)
radius = width/2.0;
if (radius > height/2.0)
radius = height/2.0;

CGFloat minx = CGRectGetMinX(rrect);
CGFloat midx = CGRectGetMidX(rrect);
CGFloat maxx = CGRectGetMaxX(rrect);
CGFloat miny = CGRectGetMinY(rrect);
CGFloat midy = CGRectGetMidY(rrect);
CGFloat maxy = CGRectGetMaxY(rrect);
CGContextMoveToPoint(context, minx, midy);
CGContextAddArcToPoint(context, minx, miny, midx, miny, radius);
CGContextAddArcToPoint(context, maxx, miny, maxx, midy, radius);
CGContextAddArcToPoint(context, maxx, maxy, midx, maxy, radius);
CGContextAddArcToPoint(context, minx, maxy, minx, midy, radius);
CGContextClosePath(context);
CGContextDrawPath(context, kCGPathFillStroke);
}

- (void)dealloc {
[strokeColor release];
[rectColor release];
[super dealloc];
}

@end





32 comments:

Hunter Hillegas said...

Weird, I tried using this and it works but the frame of the roundRect UIView is black so it looks like a rounded rect inside a black box.

That's inside a table view cell. I can't quite see why I'm seeing any black - the cell's background is white and the round rect's is clear.

Odd.

Jeff LaMarche said...

That's odd... can't think of what would cause that - are you creating your view in IB or programmatically? If programmatically, which init method are you using?

Hunter Hillegas said...

Programatically... Not much too it.

UIView *roundedRect = [[RoundedRectView alloc] initWithFrame:CGRectMake(5.0, 5.0, 120.0, 20.0)];
[cellContentView addSubview:roundedRect];
[roundedRect release];

Mikhail said...

There is a little bug in initWithCoder and initWithFrame
functions.

self.backgroundColor = [UIColor clearColor];

This call does nothing. Of course it should be:

super.backgroundColor = [UIColor clearColor];

Jeff LaMarche said...

Mikhail:

That's not correct. Although the property is defined in our superclass, it is inherited by this class. The dot notation is simply syntactic sugar that gets turned into a method call by the compiler, and that call follows the ordinary rules of message dispatch. Of course.

So ending the message to self works correctly, and should we someday decide to override or change the backgroundColor property by implementing a custom mutators, those changes wouldn't get used because we would be bypassing them and calling our superclasses' version of the mutator instead of our class' version. Of course.

lmd64 said...

Here you go, took a bit of monkeying around to figure it out (I'm somewhat of a newbie to the Mac). The trick is in clipping the path.

The two new lines are:

CGContextBeginPath(context);
and
CGContextClip(context);




- (void)drawRect:(CGRect)rect {

CGContextRef context = UIGraphicsGetCurrentContext();
CGContextBeginPath(context);

....

CGContextDrawPath(context, kCGPathFillStroke);
CGContextClip(context);

}

Robert Evans said...

I ran into the "rounded rect inside a black box" problem that Hunter had. On a hunch I tried Mikhail's advice regarding the call to super.backgroundColor rather than seld.backgroundColor and gues what?


Yep. It works properly now.

Eridius said...

Jeff, Mikhail is actually correct. You've already overridden -setBackgroundColor: so your self.backgroundColor = ... line will end up doing nothing. You need to call super's impl. Same with opaque.

sarah said...

I am using a similar code to draw a rounded rect view. I run into this code as I was searching for my solution but this code did not solve the problem either.
If I add another subview to this rounded rect view with the same size, the corners of the subview will mask the rounded corners . In other words what should we do to make any drawing inside the roundedRect view clipped to the view size without overlapping the rounded corners.

Thanks

Alex said...
This comment has been removed by the author.
Alex said...

Hi! ok, so I'm having serious troubles sub-classing UIView. I copied the code for the RoundedRectView class into .m and .h files, and instantiate an instance of RoundRectView in a view controller, whose view is added to the display list like usual.

I keep getting errors from the CG drawing methods like this whenever I try to build:

"_CGRectGetMinX", referenced from:
-[RoundedRectView drawRect:] in RoundedRectView.o

any help would be hugely appreciated

darcy brockbank said...

Some stuff:

1. For the poster with the error about "_CGRectGetMinX". It is because you didn't add the CoreGraphics framework to your build. This symbol is in that framework so without the framework linked, the linker can't resolve the symbol.

2. "self.backgroundColor=someColor" is translated into this code at compile time:

[self setBackgroundColor:someColor];

So when you simultaneously define the method to not do anything, and then call the method yourself, then nothing results.

People are more and more treating instance variables via properties like they are elements of a struct, and this is not the case.

What the posters are reacting to is this call in the init:

self.backgroundColor = [UIColor clearColor];

You are doing this to try to clear your background color, but you are simultaneously trapping the call and rendering it into nothing.

So this ends up working *if* you set a transparent background color in interface builder, and will not work if someone else has initialized the object on their own *or* put it in IB and did not set a transparent color.

The code is correctly done with a call to super.

I personally try to avoid these calls in preference for [self setBackgroundColor:] which I think is more explicit, but I'm aulde skool.

3. your call to "self.opaque" is similarly incorrect, and should be "super.opaque=NO" since you blocked off the method call.

4. otherwise great object, thanks for posting, it is a clean and nicely coded example (save for the bugs!) saved me from looking it up!

5. My suggestion is to call "rectColor" "fillColor" and you have a great reusable superclass that everything can subclass from. No idea why they didn't make UIView support this, it should have out of the box instead of having to reimplement the wheel all over the place.

Ben said...

Hello all,

Thanks so much for the code snippet and for the helpful comments. I was running into the black border thing and it was driving me nuts -- wish I'd read the whole post first!

I discovered one other glitch. When you change the RoundRect's bounds, it will not be updated -- so the corners sometimes get all screwy (because they're scaled automatically).

The quick fix is to add the following to both init methods:

self.contentMode = UIViewContentModeRedraw;

... Thanks again!

Regards, Ben.

Scott said...

Excellent work. I am using it now and it really adds polish to my app. Thanks, I will bookmark your blog.

Kyle Waltz said...

Any chance you can post an example initWithCoder? I can get the initWithRect to work just fine but I'm trying to set the colors.

Jinbaba said...

Hello Jeff,

Your posts are excellent. I know its a very odd to ask for help this way. But I guess you would not mind.

My problem is, I am building an iPhone application that will provide scratching of cards(e.g lottery scratch cards). The idea is to present two images, the masked image will lie on top of base image. When the user scratches in the masked image, the scratched portion of the masked image will be made transparent. So it will mimic card scratching.

I could not figure out the API's/technique to implement.

Kindly point out any sample code/ hints / suggestion.

Thanx !

zub said...

thanks a lot for that great code example. I had that black background poblem also! setting

self.backgroundColor

to

super.backgroundColor

solved the issue !!

Raeldor said...

Has anyone tried this with a navigation controller. I tried to show the view using presentmodalview, but it isn't transparent.

Regards

Maria said...

Hi. Thank you for posting and sharing the code.
I have developed an app with a standard rect and would like to enhanve the look and feel of my app by using rounded rect.
My curreny code is
mytextview.frame = CGRectMake(10,20,200,300)
what should i change my code above in order for me to incorporate the roundedrectview class?

It would be appreciated if you can help me with this.

Thank you

Uli Kunkel said...

may it serve you well

That's exactly what is has done - than you so much Jeff.

Adrian Falvey said...

Thanks for that. I used it in a somewhat different context then what you did and it worked really well.

K_Be said...

Sorry, i am not good say on english
this part of code:
CGFloat minx = CGRectGetMinX(rrect);
CGFloat midx = CGRectGetMidX(rrect);
CGFloat maxx = CGRectGetMaxX(rrect);
CGFloat miny = CGRectGetMinY(rrect);
CGFloat midy = CGRectGetMidY(rrect);
CGFloat maxy = CGRectGetMaxY(rrect);

It is wrong part and rect not good
This is right part of code
CGFloat minx = CGRectGetMinX(rrect);
CGFloat midx = CGRectGetMidX(rrect);
CGFloat maxx = CGRectGetMaxX(rrect) - 1;
CGFloat miny = CGRectGetMinY(rrect) + 1;
CGFloat midy = CGRectGetMidY(rrect);
CGFloat maxy = CGRectGetMaxY(rrect);

And antialiasing turn "on"

c-cheung said...

Since you're simply ignoring setBackgroundColor for the class, I suggest adding this code snippet to set the background color of the rounded rect:

self.rectColor = newBGColor;
[self setNeedsDisplay];

This will allow people to set the background of the rounded rect via a familiar method.

Steve said...
This comment has been removed by the author.
pRaMoD said...

how to get background transparent as u had for the apple.

Karan said...

Very useful. Saved me a lot of time. Thank you

André Jacobs said...

Thanks K_Be for your tip, however I found that you missed the left and bottom.

My changes look like this:

CGFloat minX = CGRectGetMinX(fillRect) + 1;
CGFloat midX = CGRectGetMidX(fillRect);
CGFloat maxX = CGRectGetMaxX(fillRect) - 1;
CGFloat minY = CGRectGetMinY(fillRect) + 1;
CGFloat midY = CGRectGetMidY(fillRect);
CGFloat maxY = CGRectGetMaxY(fillRect) - 1;

And like he said, turning on anti-aliasing works like a bomb!

CGContextRef context = UIGraphicsGetCurrentContext();

// Turn on anti-aliasing
CGContextSaveGState(context); CGContextSetShouldAntialias(context, true);
CGContextSetAllowsAntialiasing(context, true);

....

CGContextDrawPath(context, kCGPathFillStroke);

// Restore anti aliasing
CGContextSetAllowsAntialiasing(context, false);
CGContextRestoreGState(context);

phooze said...

Jeff, can I assume that this code is public domain? E.g. I can include a modified version of it in a paid-app without licensing concern?

Trevor said...

There's a much easier way of getting rounded corners in a UIView. No need to subclass; just use the view's layer.cornerRadius property. Example:

#import <QuartzCore/QuartzCore.h>
...
UIView *background = [[UIView alloc] initWithFrame:frame];
background.backgroundColor = [UIColor blackColor];
background.alpha = 0.7;
background.layer.cornerRadius = 8;
background.layer.masksToBounds = YES;

Edwin said...

scrub m65 kamagra attorney lawyer body scrub field jacket lovegra marijuana attorney injury lawyer

JeansPilot said...

JeansPilot offers the chance to buy a large variety of men’s and women’s jeans clothing from the world famous Italian Brands.
Online jeans clothing store looks for original fashion clothing sales and clearances of worldwide known designers. We participate in fashion auctions to get the lowest possible price for Top quality Clothes, Shoes and Accessories.
Buy Jeans

h4ns said...

What youre saying is completely true. I know that everybody must say the same thing, but I just think that you put it in a way that everyone can understand. I also love the images you put in here. They fit so well with what youre trying to say. Im sure youll reach so many people with what youve got to say.

Arsenal vs Huddersfield Town live streaming
Arsenal vs Huddersfield Town live streaming
Wolverhampton Wanderers vs Stoke City Live Streaming
Wolverhampton Wanderers vs Stoke City Live Streaming
Notts County vs Manchester City Live Streaming
Notts County vs Manchester City Live Streaming
Bologna vs AS Roma Live Streaming
Bologna vs AS Roma Live Streaming
Juventus vs Udinese Live Streaming
Juventus vs Udinese Live Streaming
Napoli vs Sampdoria Live Streaming
Napoli vs Sampdoria Live Streaming
Fulham vs Tottenham Hotspur Live Streaming
Fulham vs Tottenham Hotspur Live Streaming
AS Monaco vs Marseille Live Streaming
AS Monaco vs Marseille Live Streaming
Alajuelense vs Perez Zeledon Live Streaming
Alajuelense vs Perez Zeledon Live Streaming
Technology News | News Today | Live Streaming TV Channels