I am drawing Circle using UIBezierPath. I want to draw it on CGLayer so that I can cache circle and draw text on it after some event(by calling setNeedsDisplay). How should I draw UIBezierPath on CGContextRef. My code below
- (void)drawRect:(CGRect)rect
{
// Drawing code
static CGLayerRef sTextLayer = NULL;
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGRect textBounds = CGRectMake(0, 0, 200, 100);
if (sTextLayer == NULL) {
sTextLayer = CGLayerCreateWithContext(ctx, textBounds.size, NULL);
CGContextRef textCtx = CGLayerGetContext(sTextLayer);
CGContextSetRGBFillColor (textCtx, 1.0, 0.0, 0.0, 1);
UIGraphicsPushContext(textCtx);
// Draw circle
UIBezierPath *circle = [UIBezierPath bezierPathWithOvalInRect:textBounds];
[[UIColor blackColor] setFill];
circle.lineWidth = 2.0;
[circle fill];
UIGraphicsPopContext();
}
if (self.drawString) {
UIFont *font = [UIFont systemFontOfSize:13.0];
NSString *string = #"HAPPY BIRTHDAY";
[string drawInRect:textBounds withFont:font];
}
}
Get the CGPath from the bezier path and then draw using CGContextAddPath and CGContextStrokePath. You may also want to use CGContextSetStrokeColorWithColor.
You can either:
Avoid the use of the CGLayerRef altogether and just have drawRect draw the UIBezierPath with fill method (or as Wain suggests, draw the circle using Core Graphics functions).
Add a CAShapeLayer, setting its path to the CGPathRef of the UIBezierPath, and then add the text as a CATextLayer or a UILabel subview (in which case, you don't need a drawRect implementation at all).
I infer that you're worried about the efficiency of redrawing the circle, but when you render the text, it probably needs to re-render the circle anyway. You could always save a reference to the UIBezierPath or do a snapshot, but I'm not sure that's worth it. You can benchmark it and see.
Related
Hi all I need to realize borders around an UIImageView like these.
Left, top and right border like an half-moon
http://imageshack.com/a/img841/3269/o90p.png
How can i do it ?
You can user CALayer for the same.
CALayer *l = [_btn layer];
[l setMasksToBounds:YES];
[l setCornerRadius:8.0];
and add QuartzCore.framework
You may want to subclass UIView and override the drawRect: method. Then place add your image view as a subview. Your drawRect: method would look something like this (with clipsToBounds set to YES):
- (void)drawRect:(CGRect)rect
{
UIColor *fillColor = [UIColor clearColor];
UIColor *borderColor = [UIColor redColor];
float borderWidth = 2.0f;
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, fillColor.CGColor);
CGContextSetLineWidth(context, borderWidth);
CGContextSetStrokeColorWithColor(context, borderColor.CGColor);
CGContextMoveToPoint(context, CGRectGetMinX(rect), CGRectGetMidY(rect));
CGContextAddArc(context, CGRectGetMidX(rect), CGRectGetMidY(rect), rect.size.height/2, 2*M_PI, M_PI, 1);
CGContextClosePath(context);
CGContextDrawPath(context, kCGPathFillStroke);
}
Alternatively you can look into masking with a semicircle image or creating a CAShapeLayer for the image view.
You need to:
Create a CAShapeLayer
Set its path to be a CGPathRef based on view.bounds but with only two rounded corners (probably by using [UIBezierPath
bezierPathWithRoundedRect:byRoundingCorners:cornerRadii:])
Set your view.layer.mask to be the CAShapeLayer
Just take in account that this have a bad effect on performance. This might help
I have a UIView that draws different shapes. I can make a hole in my image to have it transparent, working fine, but the hole is only square,
//hole
CGRect holeRectValue = CGRectMake(300, 40, 80, 100);
CGRect holeRectIntersection = CGRectIntersection( holeRectValue, rect );
[[UIColor clearColor] setFill];
UIRectFill(holeRectIntersection);
Now I need to make the hole in the image but not as a rect, as the shape of my drawn figure,
Here is the code:
- (id)initWithFrame:(CGRect)frame forNode0:(CGPoint)node0 forNode1:(CGPoint)node1 fornode2:(CGPoint)node2 fornode3:(CGPoint)node3 fornode4:(CGPoint)node4 fornode5:(CGPoint)node5 fornode6:(CGPoint)node6 fornode7:(CGPoint)node7 fornode8:(CGPoint)node8 fornode9:(CGPoint)node9
{
self = [super initWithFrame:frame];
if (self) {
self.opaque = NO;
self.node0Pos = node0;
self.node1Pos = node1;
self.node2Pos = node2;
self.node3Pos = node3;
self.node4Pos = node4;
self.node5Pos = node5;
}
return self;
}
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect
{
//bgnd
UIGraphicsBeginImageContext(self.frame.size);
[[UIImage imageNamed:#"cat.jpeg"] drawInRect:self.bounds];
UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[[UIColor colorWithPatternImage:image] setFill];
// Drawing code
UIRectFill(rect);
// Drawing code
//1. begin new path
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextBeginPath(context);
//2. move to initial starting point
CGContextMoveToPoint(context, self.node0Pos.x, self.node0Pos.y);
//3. add lines defining shape
CGContextAddLineToPoint(context, self.node1Pos.x, self.node1Pos.y);
CGContextAddLineToPoint(context, self.node2Pos.x, self.node2Pos.y);
CGContextAddLineToPoint(context, self.node3Pos.x, self.node3Pos.y);
CGContextAddLineToPoint(context, self.node4Pos.x, self.node4Pos.y);
CGContextAddLineToPoint(context, self.node5Pos.x, self.node5Pos.y);
//4. optionally close path
CGContextClosePath(context);
CGColorRef color;
//5. draw path
color = [UIColor colorWithRed:1 green:0 blue:228/255.0f alpha:0.5].CGColor;
//CGContextSetFillColor(context, color);
CGContextSetFillColorWithColor(context, color);
CGContextDrawPath(context, kCGPathFill);
//
//hole
CGRect holeRectValue = CGRectMake(300, 40, 80, 100);
CGRect holeRectIntersection = CGRectIntersection( holeRectValue, rect );
[[UIColor clearColor] setFill];
UIRectFill(holeRectIntersection);
}
So how do I make the "hole" with my actual context path?
P.S. I was doing some masking, but it leaves a white line around the hole, so I need to avoid this.
[[UIColor clearColor] setFill];
UIRectFill(holeRectIntersection);
This doesn’t do anything—drawing with a clear color is effectively a no-op. What you most likely want to do is add the rectangle you’re trying to cut out as part of the path you’re creating, i.e. inserting a call to CGContextAddRect before your CGContextClosePath. See Filling a Path in the Quartz 2D Programming Guide.
I believe what you're looking for is the CALayer.mask property. To create a "hole", you would generate a CALayer object with an alpha channel in the shape of the hole you want to make, and then apply it to the view you want to punch the hole in.
In semi-pseudocode:
CALayer *holeMask;
UIView *myView;
//
// Build the holeMask object via whatever means,
// and set myView to the target view that you want
// to punch the hole in...
//
myView.layer.mask = holeMask
I have a label in my view. Now I want to draw a line above the label, which means you can see the label is under the line.
So far I can draw a line using quartz2D but always under the label. Is there any way to solve my problems?
You can create a CAShapeLayer like this:
CAShapeLayer *lineLayer = [CAShapeLayer layer];
lineLayer.frame = self.label.bounds;
lineLayer.strokeColor = [UIColor redColor].CGColor;
CGRect rect = CGRectMake(0, CGRectGetMidY(lineLayer.bounds), lineLayer.bounds.size.width, 2);
lineLayer.path = [UIBezierPath bezierPathWithRect:rect].CGPath;
And then add it to the UILabel like this:
[self.label.layer addSublayer:lineLayer];
To be honest, the easiest thing to do is to create a 2x2 pixel image called line#2x.png, and have the bottom 2 pixels black, the top 2 transparent, then use it as the background image for an image view. Stretch the image view to whatever width you need by using it as a pattern image. The 1x image should be a 1x1px, all black.
UIView *lineView = [[UIView alloc] initWithFrame:frame]; // Whatever frame the line needs
// Add the line image as a pattern
UIColor *patternColor = [UIColor colorWithPatternImage:[UIImage imageNamed:#"line.png"]];
lineView.backgroundColor = patternColor;
[self.view addSubview:lineView];
If this is a label you will be using a lot, you could make a sub-class of UILabel and override the drawRect function.
- (void) drawRect:(CGRect)r
{
[super drawRect:r];
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSaveGState(context);
CGContextSetStrokeColorWithColor(context, [UIColor redColor].CGColor);
CGContextSetLineWidth(context, 1.0);
CGContextMoveToPoint(context, 1.0, 1.0);
CGContextAddLineToPoint(context, self.bounds.size.width - 1.0, 1.0);
CGContextStrokePath(context);
CGContextRestoreGState(context);
}
The advantage here is that the line will be "baked" into the view and only be draw once. Other methods such as CAShapeLayer or UIView will be re-rendered every frame.
For bonus points, you can make the color and line width properties :)
I want to make something like this:
I tried to make it with core Graphics like this:
float radius = CGRectGetWidth(rect)/2.0f - self.circleBorderWidth/2.0f;
float angleOffset = 0;
UIBezierPath *aPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect))
radius:radius
startAngle:-angleOffset
endAngle:(mCircleSegs + 1)/(float)kCircleSegs*M_PI*2 - angleOffset
clockwise:YES];
CGPathRef shape = CGPathCreateCopyByStrokingPath(aPath.CGPath, NULL, 3, kCGLineCapSquare, kCGLineJoinMiter, 1.0f);
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGContextAddPath(ctx, shape);
CGContextClip(ctx);
AngleGradientLayer *angle = [[AngleGradientLayer alloc] init];
angle.colors = [NSArray arrayWithObjects:
(id)[UIColor colorWithRed:0 green:0 blue:0 alpha:1].CGColor,
(id)[UIColor colorWithRed:1 green:1 blue:1 alpha:1].CGColor,
nil];
CGContextClipToRect(ctx, self.bounds);
[angle drawInContext:ctx];
[angle release];
But I think I am on the wrong track here. It doesn't look like the example. Is CoreGraphics able to draw something like this? how?
The bezier path you use as a clip seems to be just a fraction of a circle, while in the image you show, the path is more complex : 2 fractions of a circle, linked by 2 lines, the whole path having a 'ring' shape.
This approach should work, I used it for a timer with the same kind of look.
Although I didn't used directly AngleGradientLayer, I modified its - (CGImageRef)newImageGradientInRect:(CGRect)rect method to return a UIImage.
But I had to rotate this image by + PI/2, as Pavlov gradient angular gradient starts horizontally.
I use a UIImage, because it's a background that DOESN'T change, so I saved an instance of this UIImage in my layer, and draw it whenever I update the clipping path
- (void)drawInContext:(CGContextRef)ctx
{
UIBezierPath *currentPath = [self timerPath];
// other drawing code for glow (shadow) and white stroke)
CGContextAddPath(ctx, currentPath.CGPath);
// clip !
CGContextClip(ctx);
CGContextDrawImage(ctx, self.bounds, _circularGradientImage.CGImage);
//_circularGradientImage from modified newImageGradientInRect method.
}
Here's what I get :
I want to fill the remaining part of a CGCntext after drawing a rect? How can I do that? Thank you!
But the thing is, I set the blend mode of the cgcontext kCGBlendModeClear. I want to make the little rect in the context to transparent. If a draw the background first, can I still see the image in the rect?
If you want to fill the context (whose frame is bigRect), except for a rectangle inside it (whose frame is smallRect):
CGContextBeginPath(context);
CGContextAddRect(context, bigRect);
CGContextAddRect(context, smallRect);
CGContextSetFillColorWithColor(context, color);
CGContextEOFillPath(context, bigRect);
Do the background first...
CGRect bounds = [self bounds];
[[UIColor blackColor] set];
UIBezierPath* backgroundPath = [UIBezierPath bezierPathWithRect:bounds];
[backgroundPath fill];
CGRect innerRect = CGRectMake(self.bounds-10,
self.bounds-10,
self.bounds.width-20
self.bounds.height-20);
[[UIColor redColor] set];
UIBezierPath* foregroundPath = [UIBezierPath bezierPathWithRect:innerRect];
[foregroundPath fill];
Draw your background and then use CGContextClearRect to clear the area you want to be transparent.
arbitrary shape:
// do your foreground drawing
CGPathRef arbitraryShape; // asign your arbitrary shape
CGContextBeginPath(ctx);
CGContextAddRect(ctx, bounds); // rect in full size of the context
CGContextAddPath(ctx, arbitraryShape); // set the area you dont want to be drawn on
CGContextClip(ctx);
// do your background drawing