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;
Related
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
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
I am creating my own UITableViewCells with a gradient background. I have all the logic and drawing worked out, but one thing I want to fix is the "chunkiness" around the corners of my custom cell:
alt text http://grab.by/27SM
If you zoom in on the corners, you can see what I am talking about. Here is the code I a using to generate the cell:
CGContextRef c = UIGraphicsGetCurrentContext();
CGColorSpaceRef myColorspace = CGColorSpaceCreateDeviceRGB();
CGGradientRef myGradient = nil;
CGFloat components[8] = TABLE_CELL_BACKGROUND;
CGContextSetStrokeColorWithColor(c, [[UAColor colorWithWhite:0.7 alpha:1] CGColor]);
CGContextSetLineWidth(c, 2);
CGContextSetAllowsAntialiasing(c, YES);
CGContextSetShouldAntialias(c, YES);
CGFloat minx = CGRectGetMinX(rect) , midx = CGRectGetMidX(rect), maxx = CGRectGetMaxX(rect) ;
CGFloat miny = CGRectGetMinY(rect) , maxy = CGRectGetMaxY(rect) ;
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);
CGFloat locations[2] = { 0.0, 1.0 };
CGFloat mycomponents[8] = TABLE_CELL_BACKGROUND;
CGColorSpaceRef myColorspace = CGColorSpaceCreateDeviceRGB();
myGradient = CGGradientCreateWithColorComponents(myColorspace, mycomponents, locations, 2);
CGContextDrawLinearGradient(c, myGradient, CGPointMake(minx,miny), CGPointMake(minx,maxy), 0);
CGContextRestoreGState(c);
CGContextAddPath(c, path);
CGContextStrokePath(c);
What can I do to smooth the edges while keeping a consistent edge thickness across all cells?
Your line width is set to 2 points. What's happening is that your code is calculating your bounding rect without understanding the width of the line. The result is that for every straight segment of your shape, only half of the stroke's width is visible. On the arc, the full stroke width is visible.
Here's the relevant segment of code from my app, Funversation, to draw the playing cards with rounded corners similar to what you have.
CGRect rect = [self bounds];
rect.size.width -= lineWidth;
rect.size.height -= lineWidth;
rect.origin.x += lineWidth / 2.0;
rect.origin.y += lineWidth / 2.0;
Add that before your calculation for minx, midx, maxx, etc. and the strokes for your shape should be uniform.
The other way to make the stroke consistent would be to move the AddPath and StrokePath calls up above the RestoreGState call—that is, stroke while clipped.
For a truly 2-pt-wide stroke with this solution, simply double the line width you put in the graphics state (i.e., set it to 4 pt), since half of it will be clipped out.
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.
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