How to fill a path with gradient in drawRect:? - iphone

filling a path with a solid color is easy enough:
CGPoint aPoint;
for (id pointValue in points)
{
aPoint = [pointValue CGPointValue];
CGContextAddLineToPoint(context, aPoint.x, aPoint.y);
}
[[UIColor redColor] setFill];
[[UIColor blackColor] setStroke];
CGContextDrawPath(context, kCGPathFillStroke);
I'd like to draw a gradient instead of solid red, but I am having trouble.
I've tried the code listed in the Question/Answer: Gradients on UIView and UILabels On iPhone
which is:
CAGradientLayer *gradient = [CAGradientLayer layer];
[gradient setFrame:rect];
[gradient setColors:[NSArray arrayWithObjects:(id)[[UIColor blueColor] CGColor],
(id)[[UIColor whiteColor] CGColor], nil]];
[[self layer] setMasksToBounds:YES];
[[self layer] insertSublayer:gradient atIndex:0];
However, this paints the entire view that this is in with the gradient, covering up my original path.

I would clip to the path you want to fill, and use CGContextDrawLinearGradient. Here is a simple implementation of drawRect: as an example:
- (void) drawRect:(CGRect)rect
{
// Create a gradient from white to red
CGFloat colors [] = {
1.0, 1.0, 1.0, 1.0,
1.0, 0.0, 0.0, 1.0
};
CGColorSpaceRef baseSpace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(baseSpace, colors, NULL, 2);
CGColorSpaceRelease(baseSpace), baseSpace = NULL;
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextAddEllipseInRect(context, rect);
CGContextClip(context);
CGPoint startPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect));
CGPoint endPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect));
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);
CGGradientRelease(gradient), gradient = NULL;
CGContextRestoreGState(context);
CGContextAddEllipseInRect(context, rect);
CGContextDrawPath(context, kCGPathStroke);
}

Related

issue with drawing two CGMutablePathRef

So I can't seem to be able to draw 2 CGMutablePathRef. Here's the code:
CGRect mainRect = CGRectMake(2, 2, rect.size.width-4, 210);
CGMutablePathRef mainPathRef = createRoundedRectForRect(mainRect, 4);
if (self.imageExists_){
[[UIColor colorWithRed:0 green:0 blue:0 alpha:1.0] set];
CGContextAddPath(context, mainPathRef);
CGContextClip(context);
UIGraphicsBeginImageContext(mainRect.size);
//need to flip the images to that it is drawn appropriately as CALayer uses a different coordinate system
CGContextSaveGState(context);
CGContextTranslateCTM(context, 0.0, 210);
CGContextScaleCTM(context, 1.0, -1.0);
CGContextDrawImage(context, mainRect, self.highlightItem_.highlightStoryImage.CGImage);
UIImage* scaledImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[scaledImage drawAtPoint:CGPointMake(0, 0)];
CGContextRestoreGState(context);
this draws the image just fine on the path I specified, clipped. But then I want to draw another rounded rect below it, so I did:
[[UIColor colorWithRed:0 green:0 blue:0 alpha:1.0] set];
CGFloat colors [] = {
0.20, 0.20, 0.20, 1.0,
0.17, 0.17, 0.17, 1.0
};
CGColorSpaceRef baseSpace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(baseSpace, colors, NULL, 2);
CGColorSpaceRelease(baseSpace), baseSpace = NULL;
CGContextSaveGState(context);
CGRect commentRect = CGRectMake(2, 215, rect.size.width-4, rect.size.height - 215);
CGMutablePathRef pathRef = createRoundedRectForRect(commentRect, 3);
CGContextAddPath(context, pathRef);
CGContextClip(context);
CGPoint startPoint = CGPointMake(CGRectGetMidX(commentRect), CGRectGetMinY(commentRect));
CGPoint endPoint = CGPointMake(CGRectGetMidX(commentRect), CGRectGetMaxY(commentRect));
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);
CGGradientRelease(gradient), gradient = NULL;
CGContextRestoreGState(context);
CGContextAddPath(context, pathRef);
CGContextDrawPath(context, kCGPathStroke);
Any idea why this doesn't work?
You've clipped the drawing to the first rect, but haven't reset the clipping path before drawing the second time. You need to save the graphics state before the first clipping path is invoked, I believe.
Your first snippet should look something like this:
CGContextSaveGState(...);
CGContextAddPath(...);
CGContextClip(...);
UIGraphicsBeginImageContext(...);
... rest of your drawing code...
CGContextRestoreGState(...);
and then the second code snippet.

draw gradient orientation

So I have the following code:
-
(void) drawLinearGradient:(CGRect) rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat locations[] = { 0.0, 0.3, 1.0 };
CGColorRef grayColor = [UIColor colorWithRed:37/255.f green:37/255.f
blue:37/255.f alpha:1.0].CGColor;
CGColorRef blueColor = [UIColor colorWithRed:23.0/255.0 green:171.0/255.0
blue:219.0/255.0 alpha:1.0].CGColor;
NSArray *colors = [NSArray arrayWithObjects: (id) grayColor, (id) grayColor, (id) blueColor, nil];
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace,
(CFArrayRef) colors, locations);
CGPoint startPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect));
CGPoint endPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect));
CGContextSaveGState(context);
CGContextAddRect(context, rect);
CGContextClip(context);
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);
CGContextRestoreGState(context);
CGGradientRelease(gradient);
CGColorSpaceRelease(colorSpace);
}
What this does is draw the gradient from top to bottom, how can I make it so it draws it from left to right?
Change these two lines:
CGPoint startPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect));
CGPoint endPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect));
to:
CGPoint startPoint = CGPointMake(CGRectGetMinX(rect), CGRectGetMidY(rect));
CGPoint endPoint = CGPointMake(CGRectGetMaxX(rect), CGRectGetMidY(rect));

CoreGraphics taking a while to show on a large view - can i get it to repeat pixels?

This is my coregraphics code:
void drawTopPaperBackground(CGContextRef context, CGRect rect) {
CGRect paper3 = CGRectMake(10, 14, 300, rect.size.height - 14);
CGRect paper2 = CGRectMake(13, 12, 294, rect.size.height - 12);
CGRect paper1 = CGRectMake(16, 10, 288, rect.size.height - 10);
//Shadow
CGContextSetShadowWithColor(context, CGSizeMake(0,0), 10, [[UIColor colorWithWhite:0 alpha:0.5]CGColor]);
CGPathRef path = createRoundedRectForRect(paper3, 0);
CGContextSetFillColorWithColor(context, [[UIColor blackColor] CGColor]);
CGContextAddPath(context, path);
CGContextFillPath(context);
//Layers of paper
CGContextSaveGState(context);
drawPaper(context, paper3);
drawPaper(context, paper2);
drawPaper(context, paper1);
CGContextRestoreGState(context);
}
void drawPaper(CGContextRef context, CGRect rect) {
//Shadow
CGContextSaveGState(context);
CGContextSetShadowWithColor(context, CGSizeMake(0,0), 1, [[UIColor colorWithWhite:0 alpha:0.5]CGColor]);
CGPathRef path = createRoundedRectForRect(rect, 0);
CGContextSetFillColorWithColor(context, [[UIColor blackColor] CGColor]);
CGContextAddPath(context, path);
CGContextFillPath(context);
CGContextRestoreGState(context);
//Gradient
CGContextSaveGState(context);
CGColorRef startColor = [UIColor colorWithWhite:0.92 alpha:1.0].CGColor;
CGColorRef endColor = [UIColor colorWithWhite:0.94 alpha:1.0].CGColor;
CGRect firstHalf = CGRectMake(rect.origin.x,
rect.origin.y, rect.size.width / 2, rect.size.height);
CGRect secondHalf = CGRectMake(rect.origin.x + (rect.size.width / 2),
rect.origin.y, rect.size.width / 2, rect.size.height);
drawVerticalGradient(context, firstHalf, startColor, endColor);
drawVerticalGradient(context, secondHalf, endColor, startColor);
CGContextRestoreGState(context);
CGContextSaveGState(context);
CGRect redRect = rectForRectWithInset(rect, -1);
CGMutablePathRef redPath = createRoundedRectForRect(redRect, 0);
CGContextSaveGState(context);
CGContextSetStrokeColorWithColor(context, [[UIColor blackColor] CGColor]);
CGContextAddPath(context, path);
CGContextClip(context);
CGContextAddPath(context, redPath);
CGContextSetShadowWithColor(context, CGSizeMake(0, 0), 15.0, [[UIColor colorWithWhite:0 alpha:0.1] CGColor]);
CGContextStrokePath(context);
CGContextRestoreGState(context);
}
The view is a UIScrollView, which contains a textview. Every time the user types something and goes onto a new line, I call [self setNeedsDisplay]; and it redraws the code. But when the view starts to get long - around 1000 height, it has very noticeable lag. How can i make this code more efficient? Can i take a line of pixels and make it just repeat that, or stretch it, all the way down?
You can draw your repeating background once, into an offscreen context, pull out the image from it, then create a UIColor with the pattern image and set that as your background colour. The image will then be tiled for you.
You start an image context with UIGraphicsBeginImageContextWithOptions(). The options include the size of your expected image - I assume this is the width of your scrollview and x pixels high, the opacity, and scale. Send 0 for the scale for automatic retina support.
You can then act as if you were within a drawRect method - so your functions above can be called as normal.
Then, extract the image:
UIImage *background = UIGraphicsGetImageFromCurrentImageContext();
End the image context:
UIGraphicsEndImageContext();
Then create the colour:
UIColor *backgroundColor = [UIColor colorWithPatternImage:background];
And set that as the background for your scrollview. Typed on a phone, so apologies for ... everything.

iOS - How to draw a transparent triangle in view

I am trying to do a custom tab bar with a transparent triangle that points into the tab bar's view.
Right now I am drawing a linear gradient and a gloss in the drawRect method for the background of this tab bar. I just need to add the transparent triangle on there. I know how to draw a triangle. I just need to know how to make it transparent to show the background beneath the tab bar view.
Anyone know how to do this?
Update
Here is the current code:
void drawGlossAndGradient(CGContextRef context, CGRect rect, CGColorRef startColor, CGColorRef endColor)
{
drawLinearGradient(context, rect, startColor, endColor);
CGColorRef glossColor1 = [UIColor colorWithRed:1.0 green:1.0
blue:1.0 alpha:0.35].CGColor;
CGColorRef glossColor2 = [UIColor colorWithRed:1.0 green:1.0
blue:1.0 alpha:0.1].CGColor;
CGRect topHalf = CGRectMake(rect.origin.x, rect.origin.y,
rect.size.width, rect.size.height/2);
drawLinearGradient(context, topHalf, glossColor1, glossColor2);
}
void drawLinearGradient(CGContextRef context, CGRect rect, CGColorRef startColor, CGColorRef endColor)
{
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGFloat locations[] = { 0.0, 1.0 };
NSArray *colors = [NSArray arrayWithObjects:(__bridge id)startColor, (__bridge id)endColor, nil];
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (__bridge CFArrayRef) colors, locations);
CGPoint startPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMinY(rect));
CGPoint endPoint = CGPointMake(CGRectGetMidX(rect), CGRectGetMaxY(rect));
CGContextSaveGState(context);
CGContextAddRect(context, rect);
CGContextClip(context);
CGContextDrawLinearGradient(context, gradient, startPoint, endPoint, 0);
CGContextRestoreGState(context);
CGGradientRelease(gradient);
CGColorSpaceRelease(colorSpace);
}
- (void)drawTriangle
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGPoint pt1 = CGPointMake(0.0f, 0.0f);
CGPoint pt2 = CGPointMake(10.0f, 10.0f);
CGPoint pt3 = CGPointMake(20.0f, 0.0f);
CGPoint vertices[] = {pt1, pt2, pt3, pt1};
CGContextBeginPath(context);
CGContextAddLines(context, vertices, 3);
CGContextClosePath(context);
CGContextClip(context);
}
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGColorRef lightColor = [UIColor colorWithRed:65.0f/255.0f green:64.0f/255.0f
blue:66.0f/255.0f alpha:1.0].CGColor;
CGColorRef darkColor = [UIColor colorWithRed:37.0/255.0 green:31.0/255.0
blue:32.0/255.0 alpha:1.0].CGColor;
[self drawTriangle];
CGRect viewRect = self.bounds;
drawGlossAndGradient(context, viewRect, lightColor, darkColor);
}
I added the clip suggested below but that just made my background with the gradient and the gloss dissappear and the triangle become gray. Does anyone know what I am doing wrong here?
If you draw this gradient in drawRect: method just add clipping path before it.
Example:
-(void)drawRect:(CGRect)rect {
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGPoint vertices[] = {coordinates of vertices};
CGContextBeginPath(ctx);
CGContextAddLines(ctx, vertices, sizeof(vertices)/sizeof(CGPoint));
CGContextClosePath(ctx);
CGContextClip(ctx);
// draw the gradient
}
Vertices - is an array with 7 points. 1 point per each corner of self.bounds and 3 points which are define the triangle.
For example:
(0) (1) (3) (4)
_____ ________________
| \ / |
| V |
| (2) |
(6) |_______________________|(5)
CGPoint vertices[7] = {CGPointZero, // origin
p1, p2, p3, // vertices of the triangle
{self.bounds.size.width, 0},
{self.bounds.size.width, self.bounds.size.height},
{0, self.bounds.size.height}
}
If anybody needs, this is a code to draw a colored triangle in a UITableView without using UIImageView. This method is good because, the size of triangle can vary and it is drawn programmatically. Also, you can probably change colors and make triangle transparent.
#implementation RRTriangleView
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, 0.0, 0.0);
CGPathAddLineToPoint(path, NULL, - self.bounds.size.height / 2.0, self.bounds.size.height / 2.0);
CGPathAddLineToPoint(path, NULL, 0.0, self.bounds.size.height);
CGPathAddLineToPoint(path, NULL, 0.0f, 0.0f);
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
[shapeLayer setPath:path];
[shapeLayer setFillColor:[COLOR_CUSTOM_LIGHT_BLUE CGColor]];
[shapeLayer setStrokeColor:[COLOR_CUSTOM_LIGHT_BLUE CGColor]];
[shapeLayer setPosition:CGPointMake(self.bounds.size.width, 0.0f)];
[[self layer] addSublayer:shapeLayer];
CGPathRelease(path);
}
return self;
}
Init this TriangleView and add it to your cell, when it is selected :
- (RRTriangleView *)triangleView
{
if (! _triangleView) {
_triangleView = [[RRTriangleView alloc] initWithFrame:self.bounds];
_triangleView.layer.backgroundColor = [[UIColor clearColor] CGColor];
_triangleView.clipsToBounds = NO;
}
return _triangleView;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
//[super setSelected:selected animated:animated];
if (selected) {
[self addSubview:self.triangleView];
}
else {
[self.triangleView removeFromSuperview];
}
}
The size of triangleView is like your cell's view, it is transparent and it is drawn above.
I beleive that
[view setBackgroundColor:[UIColor clearColor]];
[view setOpaque:NO];
will make your view transparent.

CGContextSetShadowWithColor not working

This code should be showing a shadow, but it isn't:
CGContextRef context = UIGraphicsGetCurrentContext();
//Border
CGMutablePathRef outerPath = createRoundedRectForRect(self.bounds, MENU_BUTTON_OUTER_RADIUS);
CGContextSetFillColorWithColor(context, [[UIColor colorWithWhite:0 alpha:0.18] CGColor]);
CGContextAddPath(context, outerPath);
CGContextFillPath(context);
//Button
UIColor *buttonColor;
if (self.type == JMenuButtonTypeBlack) {
buttonColor = [UIColor colorWithWhite:0 alpha:1.0];
}
else if (self.type == JMenuButtonTypeWhite) {
buttonColor = [UIColor colorWithWhite:0.72 alpha:1.0];
}
CGRect insideRect = rectForRectWithInset(self.bounds, 3);
CGMutablePathRef innerPath = createRoundedRectForRect(insideRect, MENU_BUTTON_INNER_RADIUS);
CGPoint gradientTop = CGPointMake(0, insideRect.origin.y);
CGPoint gradientBottom = CGPointMake(0, insideRect.origin.y + insideRect.size.height);
//Base color
CGContextSaveGState(context);
CGContextSetFillColorWithColor(context, [buttonColor CGColor]);
CGContextAddPath(context, innerPath);
CGContextFillPath(context);
CGContextRestoreGState(context);
//Gradient 1
CGContextSaveGState(context);
CGFloat colors [] = {
1.0, 1.0, 1.0, 0.16,
0.0, 0.0, 0.0, 0.11
};
CGColorSpaceRef baseSpace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColorComponents(baseSpace, colors, NULL, 2);
CGContextAddPath(context, innerPath);
CGContextClip(context);
CGContextDrawLinearGradient(context, gradient, gradientTop, gradientBottom, 0);
CGGradientRelease(gradient), gradient = NULL;
CGColorSpaceRelease(baseSpace), baseSpace = NULL;
CGContextRestoreGState(context);
//Shadow
CGContextSaveGState(context);
CGContextAddPath(context, innerPath);
CGContextSetShadowWithColor(context, CGSizeMake(0, 2), 3.0, [[UIColor blackColor] CGColor]);
CGContextRestoreGState(context);
This is what it looks like so far. The shadow code doesn't make a difference:
You're not drawing anything after you set the shadow. You need to either stroke or fill (CGContextStrokePath() or CGContextFillPath()) the path in order for it to be drawn into the context and thus appear on screen.