iOS Draw Rectagle with curved ends - iphone

Imagine a long rectangle (maybe with size 200x20). All sides have straight edges. In my iOS app, this is easy for me to draw:
CGContextFillRect(context, CGRectMake(xLoc, yLoc, 200, 20));
Now what if I wanted the shorter ends (on the left and right side of the rectangle) to be slightly curved, rather than straight edges. What would the code look like to do this?
(Note that I am relatively inexperienced with CGContext drawing)

Something like this (untested, so beware of bugs!):
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetStrokeColorWithColor(context, [[UIColor blackColor] CGColor]);
CGContextSetRGBFillColor(context, 0.0, 0.0, 1.0, 1.0);
CGRect rrect = CGRectMake(CGRectGetMinX(rect), CGRectGetMinY(rect), CGRectGetWidth(rect)-30, CGRectGetHeight(rect)-30);
CGFloat radius = 0.5f;
CGFloat minx = CGRectGetMinX(rrect), midx = CGRectGetMidX(rrect), maxx = CGRectGetMaxX(rrect);
CGFloat miny = CGRectGetMinY(rrect), midy = CGRectGetMidY(rrect), 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);
}

You may find Jeff LaMarche's RoundedRectView class useful, whether to use instances of directly or even to see how he makes them:
http://iphonedevelopment.blogspot.com/2008/11/creating-transparent-uiviews-rounded.html

Related

UIView with rounded rectangle and drop shadow: shadow appears above rectangle

I've got a uiview subclass where I'm trying to draw a rounded rectangle with a drop shadow. Though it draws both elements, I can see shadow through the rounded rectangle fill. I'm new to CG so I'm probably missing something simple (though it doesn't seem to be the alpha of the fill which is set to 1). Here's the draw rect code.
- (void)drawRect:(CGRect)rect {
// get the contect
CGContextRef context = UIGraphicsGetCurrentContext();
//for the shadow, save the state then draw the shadow
CGContextSaveGState(context);
CGContextSetShadow(context, CGSizeMake(4,-5), 10);
//now draw the rounded rectangle
CGContextSetStrokeColorWithColor(context, [[UIColor blackColor] CGColor]);
CGContextSetRGBFillColor(context, 0.0, 0.0, 1.0, 1.0);
//since I need room in my rect for the shadow, make the rounded rectangle a little smaller than frame
CGRect rrect = CGRectMake(CGRectGetMinX(rect), CGRectGetMinY(rect), CGRectGetWidth(rect)-30, CGRectGetHeight(rect)-30);
CGFloat radius = self.cornerRadius;
// the rest is pretty much copied from Apples example
CGFloat minx = CGRectGetMinX(rrect), midx = CGRectGetMidX(rrect), maxx = CGRectGetMaxX(rrect);
CGFloat miny = CGRectGetMinY(rrect), midy = CGRectGetMidY(rrect), maxy = CGRectGetMaxY(rrect);
// Start at 1
CGContextMoveToPoint(context, minx, midy);
// Add an arc through 2 to 3
CGContextAddArcToPoint(context, minx, miny, midx, miny, radius);
// Add an arc through 4 to 5
CGContextAddArcToPoint(context, maxx, miny, maxx, midy, radius);
// Add an arc through 6 to 7
CGContextAddArcToPoint(context, maxx, maxy, midx, maxy, radius);
// Add an arc through 8 to 9
CGContextAddArcToPoint(context, minx, maxy, minx, midy, radius);
// Close the path
CGContextClosePath(context);
// Fill & stroke the path
CGContextDrawPath(context, kCGPathFillStroke);
//for the shadow
CGContextRestoreGState(context);
}
- (void)drawRect:(CGRect)rect
{
self.layer.masksToBounds = NO;
self.layer.shadowColor = [[UIColor whiteColor] CGColor];
self.layer.shadowOffset = CGSizeMake(0,2);
self.layer.shadowRadius = 2;
self.layer.shadowOpacity = 0.2;
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(20, 20)];
[[UIColor blackColor] setFill];
[path fill];
}
I don't think you can do this in a single pass. Hmm I changed your code as follows, which seems to work.
- (void)drawRect:(CGRect)rect
{
// get the contect
CGContextRef context = UIGraphicsGetCurrentContext();
//now draw the rounded rectangle
CGContextSetStrokeColorWithColor(context, [[UIColor blackColor] CGColor]);
CGContextSetRGBFillColor(context, 0.0, 0.0, 1.0, 0.0);
//since I need room in my rect for the shadow, make the rounded rectangle a little smaller than frame
CGRect rrect = CGRectMake(CGRectGetMinX(rect), CGRectGetMinY(rect), CGRectGetWidth(rect)-30, CGRectGetHeight(rect)-30);
CGFloat radius = 45;
// the rest is pretty much copied from Apples example
CGFloat minx = CGRectGetMinX(rrect), midx = CGRectGetMidX(rrect), maxx = CGRectGetMaxX(rrect);
CGFloat miny = CGRectGetMinY(rrect), midy = CGRectGetMidY(rrect), maxy = CGRectGetMaxY(rrect);
{
//for the shadow, save the state then draw the shadow
CGContextSaveGState(context);
// Start at 1
CGContextMoveToPoint(context, minx, midy);
// Add an arc through 2 to 3
CGContextAddArcToPoint(context, minx, miny, midx, miny, radius);
// Add an arc through 4 to 5
CGContextAddArcToPoint(context, maxx, miny, maxx, midy, radius);
// Add an arc through 6 to 7
CGContextAddArcToPoint(context, maxx, maxy, midx, maxy, radius);
// Add an arc through 8 to 9
CGContextAddArcToPoint(context, minx, maxy, minx, midy, radius);
// Close the path
CGContextClosePath(context);
CGContextSetShadow(context, CGSizeMake(4,-5), 10);
CGContextSetStrokeColorWithColor(context, [[UIColor blackColor] CGColor]);
// Fill & stroke the path
CGContextDrawPath(context, kCGPathFillStroke);
//for the shadow
CGContextRestoreGState(context);
}
{
// Start at 1
CGContextMoveToPoint(context, minx, midy);
// Add an arc through 2 to 3
CGContextAddArcToPoint(context, minx, miny, midx, miny, radius);
// Add an arc through 4 to 5
CGContextAddArcToPoint(context, maxx, miny, maxx, midy, radius);
// Add an arc through 6 to 7
CGContextAddArcToPoint(context, maxx, maxy, midx, maxy, radius);
// Add an arc through 8 to 9
CGContextAddArcToPoint(context, minx, maxy, minx, midy, radius);
// Close the path
CGContextClosePath(context);
CGContextSetStrokeColorWithColor(context, [[UIColor blackColor] CGColor]);
CGContextSetRGBFillColor(context, 0.0, 0.0, 1.0, 1.0);
// Fill & stroke the path
CGContextDrawPath(context, kCGPathFillStroke);
}
}
Try this after you import QuartzCore/QuartzCore.h
yourView.layer.shadowColor = [[UIColor blackColor] CGColor];
yourView.layer.shadowOffset = CGSizeMake(10.0f, 10.0f);
yourView.layer.shadowOpacity = 1.0f;
yourView.layer.shadowRadius = 10.0f;

Drawing a Shadow after the CGContextClip with Core Graphics

UPDATE:
I tried implementing the method specified by Peter and am getting incorrect shadowing. What is wrong?
alt text http://grab.by/2XyP
CGContextSetShadowWithColor(c, CGSizeMake(4, 4), kAudioThumbShadowBlur, [[UAColor blackColor] CGColor]);
CGContextFillPath(c);
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, minx, midy);
CGPathAddArcToPoint(path, NULL, minx, miny, midx, miny, kDefaultMargin);
CGPathAddArcToPoint(path, NULL, maxx, miny, maxx, midy, kDefaultMargin);
CGPathAddArcToPoint(path, NULL, maxx, maxy, midx, maxy, kDefaultMargin);
CGPathAddArcToPoint(path, NULL, minx, maxy, minx, midy, kDefaultMargin);
CGPathCloseSubpath(path);
// Fill and stroke the path
CGContextSaveGState(c);
CGContextAddPath(c, path);
CGContextClip(c);
myGradient = CGGradientCreateWithColorComponents(myColorspace, components, locations, 2);
CGContextDrawLinearGradient(c, myGradient, CGPointMake(minx,miny), CGPointMake(minx,maxy), 0);
CGContextAddPath(c, path);
CGPathRelease(path);
CGContextStrokePath(c);
CGContextRestoreGState(c);
ORIGINAL QUESTION:
I am looking to draw a natural looking shadow around the bottom of a custom rounded cell item I make in CoreGraphics using this code:
...
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, minx, miny);
CGPathAddArcToPoint(path, NULL, minx, maxy, midx, maxy, kDefaultMargin);
CGPathAddArcToPoint(path, NULL, maxx, maxy, maxx, miny, kDefaultMargin);
CGPathAddLineToPoint(path, NULL, maxx, miny);
CGPathAddLineToPoint(path, NULL, minx, miny);
CGPathCloseSubpath(path);
// Fill and stroke the path
CGContextSaveGState(c);
CGContextAddPath(c, path);
CGContextClip(c);
myGradient = CGGradientCreateWithColorComponents(myColorspace, components, locations, 2);
CGContextDrawLinearGradient(c, myGradient, CGPointMake(minx,miny), CGPointMake(minx,maxy), 0);
CGContextAddPath(c, path);
CGPathRelease(path);
CGContextStrokePath(c);
CGContextRestoreGState(c);
...
I want to apply the shadow around the outside of this path, either before or after the gradient fill. What is the best way to go about doing this?
A gradient draw doesn't count as a fill, so, first, set the shadow and do a solid-color fill. Then, draw over the solid-color fill with the gradient and clipped stroke.

Rounded UIView with Shadow?

So using this link:
How do I draw a shadow under a UIView?
And this link:
http://iphonedevelopment.blogspot.com/2008/11/creating-transparent-uiviews-rounded.html
I came upon this implementation:
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextSetShadow(context, CGSizeMake(-15.0f, 20.0f), 5.0f);
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);
CGContextRestoreGState(context);
}
However when it draws, the shadow is clipped to the bounds of the view. I have tried setting self.clipsToBounds = NO however this doesn't affect the problem.
How about drawing the shadow using Quartz Core instead? Something like:
view.layer.shadowColor = [UIColor blackColor].CGColor;
view.layer.shadowOffset = CGSizeMake(0, 0);
view.layer.shadowRadius = 4;
view.layer.shadowOpacity = 1;
view.layer.shadowPath = [UIBezierPath bezierPathWithRoundedRect:view.frame cornerRadius:11].CGPath; // make sure you set that for better performance

CoreGraphics on iPhone, trying to draw a "pill" type ellipse

I'm trying to draw a pill type ellipse, as in Apple's Mail application which displays the number of emails in the inbox. Any idea why the following isn't drawing?
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat minX = CGRectGetMinX(rect);
CGFloat minY = CGRectGetMinY(rect);
CGFloat maxX = CGRectGetMaxX(rect);
CGFloat maxY = CGRectGetMaxY(rect);
CGFloat radius = 3.0;
CGContextBeginPath(context);
CGContextMoveToPoint(context, (minX + maxX) / 2.0, minY);
CGContextAddArcToPoint(context, minX, minY, minX, maxY, radius);
CGContextAddArcToPoint(context, minX, maxY, maxX, maxY, radius);
CGContextAddArcToPoint(context, maxX, maxY, maxX, minY, radius);
CGContextAddArcToPoint(context, maxX, minY, minX, minY, radius);
CGContextClosePath(context);
CGContextDrawPath(context, kCGPathFill);
CGContextFillPath(context);
}
I think you've forgotten to set the fill color.
Also, see this question for code to do exactly what you require.

How can I clear the corners outside of a rounded rect using the iPhone SDK?

I am attempting to implement a custom view. This view should display an image surrounded by a gray, rounded rect border. I can get the image to display fine, as well as the border, however, since the border has rounded corners, I need a way to clear those corners such that they correctly display whatever is behind the view. How can I accomplish this?
It seems like I might be able to use CGContextClearRect, but then wouldn't I have to call this multiple times, reconstructing the area outside of my rounded corner? That sounds overly complicated.
Is there a better way to create this view?
Here is my current code:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
// Draw the image. This will completely fill the current rect.
[image drawInRect:self.bounds];
// Ensure we draw completely within our bounds instead of straddling it.
CGRect rrect = self.bounds;
rrect.size.height = rrect.size.height - 1.0;
rrect.size.width = rrect.size.width - 1.0;
rrect.origin.x = rrect.origin.x + (1.0 / 2);
rrect.origin.y = rrect.origin.y + (1.0 / 2);
CGFloat radius = 5.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);
// Draw the rounded rect border.
CGContextSetRGBStrokeColor(context, 0.6, 0.6, 0.6, 1.0);
CGContextSetRGBFillColor(context, 1.0, 1.0, 1.0, 0.0);
CGContextSetLineWidth(context, 1.0);
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);
}
Add the rounded-rect path to the clipping path before drawing the image.
Last week I was trying to do something similar.
With your question, the answer and other Q&A I created an example with a solution.
https://github.com/GabrielMassana/TransparentRoudRectButton