appendPath to produce hollow object Core Graphics - iphone

So I am trying to create a segment of a circle ie a pie slice then use a circle to remove the majority of the wedge leaving an outer arc.
This is what I have so far
As you can see it is messing up somewhere!
I achieve this using the following code:-
UIBezierPath* theStroke = [UIBezierPath bezierPathWithRoundedRect:mainCutout cornerRadius:theRadius];
[theOrangeColor setFill];
theStroke.usesEvenOddFillRule = YES;
[theStroke fill];
[theStroke stroke];
[theStroke setMiterLimit:2.0];
UIBezierPath *aSegment = [UIBezierPath bezierPath];
aSegment.usesEvenOddFillRule = YES;
[aSegment moveToPoint:theCenter];
[aSegment addLineToPoint:theCenter];
[aSegment addArcWithCenter:theCenter radius:theRadius startAngle:startAngle endAngle:endAngle clockwise:YES];
[aSegment addLineToPoint:theCenter];
[aSegment appendPath:theStroke];
[theRedColor setFill];
[aSegment fill];
[aSegment stroke];
[aSegment closePath];
Can anyone help me?

I do not see how you can use the even-odd filling rule to remove that parts of the sector.
But you can easily draw the "outer slice" of the segment by drawing two arc segments with different radii. Example:
CGPoint theCenter = CGPointMake(100., 100.);
CGFloat innerRadius = 50.;
CGFloat outerRadius = 60.;
CGFloat startAngle = M_PI;
CGFloat endAngle = 3*M_PI/2;
UIBezierPath *aSegment = [UIBezierPath bezierPath];
[aSegment addArcWithCenter:theCenter radius:innerRadius startAngle:startAngle endAngle:endAngle clockwise:YES];
[aSegment addArcWithCenter:theCenter radius:outerRadius startAngle:endAngle endAngle:startAngle clockwise:NO];
[aSegment closePath];
[[UIColor redColor] setFill];
[aSegment fill];
Result:

With a slight modification in #Martin R's code,
If you can specify the lineWidth of the CALayer over which you gonna draw your arc segment, a single "addArcWithCenter:radius:startAngle:endAngle:clockwise:" will do the job for you.
You have to use stroke instead of fill.(i.e.)just use strokeColor
Eg:
static inline double radians (double degrees)
{
return degrees * M_PI/180;
}
CAShapeLayer * shapeLayer = [CAShapeLayer layer];
shapeLayer.lineWidth = 10;
shapeLayer.fillColor = [UIColor clearColor].CGColor;
shapeLayer.strokeColor = [UIColor redColor].CGColor;
CGFloat radius = 50.0;
shapeLayer.path = [[UIBezierPath bezierPathWithArcCenter:centerPoint radius:radius startAngle:radians(startingAngle) endAngle:radians(endingAngle) clockwise:1 ]CGPath ];
[self.layer addSublayer:shapeLayer];
This may be useful to someone...

Related

Draw hollow circle divided into equal parts

I want draw a hollow circle that look like the below image
I got the below code snippet to draw hollow circle
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
// Initialization code
self.backgroundColor = [UIColor whiteColor];
// Determine our start and stop angles for the arc (in radians)
startAngle = M_PI * 1.5;
endAngle = startAngle + (M_PI * 2);
}
return self;
}
- (void)drawRect:(CGRect)rect
{
// Display our percentage as a string
NSString* textContent = [NSString stringWithFormat:#"%d", self.percent];
UIBezierPath* bezierPath = [UIBezierPath bezierPath];
// Create our arc, with the correct angles
[bezierPath addArcWithCenter:CGPointMake(rect.size.width / 2, rect.size.height / 2)
radius:130
startAngle:startAngle
endAngle:(endAngle - startAngle) * (_percent / 100.0) + startAngle
clockwise:YES];
// Set the display for the path, and stroke it
bezierPath.lineWidth = 50;
[[UIColor redColor] setStroke];
[bezierPath stroke];
// Text Drawing
CGRect textRect = CGRectMake((rect.size.width / 2.0) - 71/2.0, (rect.size.height / 2.0) - 45/2.0, 71, 45);
[[UIColor blackColor] setFill];
[textContent drawInRect: textRect withFont: [UIFont fontWithName: #"Helvetica-Bold" size: 42.5] lineBreakMode: NSLineBreakByWordWrapping alignment: NSTextAlignmentCenter];
}
I need the separator lines also in the hollow circle,as in the above image.Any suggestion will be helpful ....
Take a look at HKCircularProgressView.
I'm using it for my own project, extending the functionality for your need should be easy.
Not answering exactly what was asked, but a Simple solution for dividing any circle in three equal parts that solved my purpose-
- (void)drawRect:(CGRect)rect {
CGPoint center = CGPointMake(CGRectGetWidth(self.bounds) / 2.f, CGRectGetHeight(self.bounds) / 2.f);
CGFloat radius = 8.f;
UIBezierPath *portionPath1 = [UIBezierPath bezierPath];
[portionPath1 moveToPoint:center];
[portionPath1 addArcWithCenter:center radius:radius startAngle:radians(0) endAngle:radians(120) clockwise:YES];
[portionPath1 closePath];
[[UIColor greenColor] setFill];
[portionPath1 fill];
UIBezierPath *portionPath2 = [UIBezierPath bezierPath];
[portionPath2 moveToPoint:center];
[portionPath2 addArcWithCenter:center radius:radius startAngle:radians(120) endAngle:radians(240) clockwise:YES];
[portionPath2 closePath];
[[UIColor yellowColor] setFill];
[portionPath2 fill];
UIBezierPath *portionPath3 = [UIBezierPath bezierPath];
[portionPath3 moveToPoint:center];
[portionPath3 addArcWithCenter:center radius:radius startAngle:radians(240) endAngle:radians(360) clockwise:YES];
[portionPath3 closePath];
[[UIColor blueColor] setFill];
[portionPath3 fill]; }
At top put these two lines.
#define PI 3.14159265358979323846
static inline float radians(double degrees) { return degrees * PI / 180; }
Hope this will help anybody else too. :)

Optimization of drawing lines, possible alternatives to CAShapeLayer

I need to draw many lines (in the range of 50-75) in a screen and currently use the below function for it which works fine. After drawing 40-50 of these lines with the below code, the application slows down noticeably in my iPhone 4. To optimize I tried removing the line shadow it helped but still app wasn't running as smooth as I wanted. I need to optimize the below code, my first idea is to replace the cashapelayers with .png line images. But the new method should support line rotation, different length line with same width, and animation of drawing (it seems a lot for me to do with cgaffinetransforms). Any ideas that can help me?
+ (CAShapeLayer *) drawLineOnView:(UIView *) view BetweenPoint1:(CGPoint) point1 Point2:(CGPoint) point2 lineWidth:(CGFloat)lineWidth lineColor:(UIColor *) color Animated:(BOOL) animed
{
CAShapeLayer *lineShape = [CAShapeLayer layer];
CGMutablePathRef linePath = nil;
linePath = CGPathCreateMutable();
//lineShape.opacity = 0.6;
lineShape.lineWidth = lineWidth;
lineShape.lineCap = kCALineCapRound;
if(color==nil) color = [UIColor orangeColor]; //Default value
lineShape.shadowColor = [color CGColor];
lineShape.shadowOpacity = 1.0;
lineShape.shadowRadius = 5.0;
lineShape.strokeColor = [color CGColor];
CGPathMoveToPoint(linePath, NULL, point1.x, point1.y);
CGPathAddLineToPoint(linePath, NULL, point2.x, point2.y);
if(animed)
{
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
pathAnimation.duration = 1.0;
pathAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
pathAnimation.toValue = [NSNumber numberWithFloat:1.0f];
[lineShape addAnimation:pathAnimation forKey:#"strokeEndAnimation"];
}
lineShape.path = linePath;
CGPathRelease(linePath);
[view.layer addSublayer:lineShape];
return lineShape;
}
PARTLY SOLVED (Optimization never ends)
I broke down my line drawing function into 2 complementary parts and draw multiple lines into the one shape layer instead of creating new layers each time. It works much better if not great. Here is the updated code:
+ (CAShapeLayer *) createNewShapeLayerForDrawingLinesOnView:(UIView *) view lineWidth:(CGFloat)lineWidth lineColor:(UIColor *) color
{
CAShapeLayer *lineShape = [CAShapeLayer layer];
//lineShape.opacity = 0.6;
lineShape.lineWidth = lineWidth;
lineShape.lineCap = kCALineCapRound;
if(color==nil) color = [UIColor orangeColor]; //Default value
lineShape.shadowColor = [color CGColor];
lineShape.shadowOpacity = 1.0;
lineShape.shadowRadius = 5.0;
lineShape.strokeColor = [color CGColor];
[view.layer addSublayer:lineShape];
return lineShape;
}
+ (void) addNewLineToShapeLayer:(CAShapeLayer *) shapeLayer BetweenPoint1:(CGPoint) point1 Point2:(CGPoint) point2
{
CGMutablePathRef combinedPath = CGPathCreateMutableCopy(shapeLayer.path);
CGMutablePathRef linePath = CGPathCreateMutable();
CGPathMoveToPoint(linePath, NULL, point1.x, point1.y);
CGPathAddLineToPoint(linePath, NULL, point2.x, point2.y);
//No paths drawn before
if(combinedPath == NULL)
{
combinedPath = linePath;
}
else
{
CGPathAddPath(combinedPath, NULL, linePath);
}
shapeLayer.path = combinedPath;
CGPathRelease(linePath);
}
While I understand the want to create multiple layers, it will be much more efficient to draw all the lines into one and to manage animations and rotations of a list of lines from there. You can do this in a shape layer with a combined path like(missing code marked with "..."):
CGMutablePathRef combinedPath = CGPathCreateMutableCopy(path.CGPath);
for(...)
CGPathAddPath(combinedPath, NULL, [self makeNewPathFrom:...].CGPath);
myLayer.path = combinedPath;
Even faster, you can draw the list of lines directly onto the graphics context of a CALayer. This example for a view's drawRect: method is untested but should give you an idea:
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineWidth(context, lineWidth);
CGContextSetRGBStrokeColor(context, 1.0, 0.0, 0.0, 1.0); //red
for(MyLine *line in lines)
{
CGContextMoveToPoint(context, point1.x, point1.y);
CGContextAddLineToPoint(context, point2.x, point2.y);
}
If you need further optimization, you should look into OpenGL.
You definitely do not want 75 layers, each with their own line. Are you sure you can't draw a single, more complex, path in a single layer?

Drawing Shadowed Rectangle at iOS

I am trying to draw an image like below with libraries in iOS; but i couldn't.
I think it is very easy to draw but i couldn't achieve.
After i accomplish to draw i will place a label over it.
Use this as your drawRect method:
- (void)drawRect:(CGRect)rect
//// General Declarations
CGContextRef context = UIGraphicsGetCurrentContext();
//// Shadow Declarations
UIColor* shadow = [UIColor blackColor];
CGSize shadowOffset = CGSizeMake(1, 1);
CGFloat shadowBlurRadius = 2;
//// Frames
CGRect frame = rect;
//// Abstracted Graphic Attributes
CGRect shadowBoxRect = CGRectMake(CGRectGetMinX(frame) + 0, CGRectGetMinY(frame) + 0, 40, 40);
CGFloat shadowBoxCornerRadius = 4;
//// ShadowBox Drawing
UIBezierPath* shadowBoxPath = [UIBezierPath bezierPathWithRoundedRect: shadowBoxRect cornerRadius: shadowBoxCornerRadius];
[[UIColor lightGrayColor] setFill];
[shadowBoxPath fill];
////// ShadowBox Inner Shadow
CGRect shadowBoxBorderRect = CGRectInset([shadowBoxPath bounds], -shadowBlurRadius, -shadowBlurRadius);
shadowBoxBorderRect = CGRectOffset(shadowBoxBorderRect, -shadowOffset.width, -shadowOffset.height);
shadowBoxBorderRect = CGRectInset(CGRectUnion(shadowBoxBorderRect, [shadowBoxPath bounds]), -1, -1);
UIBezierPath* shadowBoxNegativePath = [UIBezierPath bezierPathWithRect: shadowBoxBorderRect];
[shadowBoxNegativePath appendPath: shadowBoxPath];
shadowBoxNegativePath.usesEvenOddFillRule = YES;
CGContextSaveGState(context);
{
CGFloat xOffset = shadowOffset.width + round(shadowBoxBorderRect.size.width);
CGFloat yOffset = shadowOffset.height;
CGContextSetShadowWithColor(context,
CGSizeMake(xOffset + copysign(0.1, xOffset), yOffset + copysign(0.1, yOffset)),
shadowBlurRadius,
shadow.CGColor);
[shadowBoxPath addClip];
CGAffineTransform transform = CGAffineTransformMakeTranslation(-round(shadowBoxBorderRect.size.width), 0);
[shadowBoxNegativePath applyTransform: transform];
[[UIColor grayColor] setFill];
[shadowBoxNegativePath fill];
}
CGContextRestoreGState(context);
}
Inner shadows are hard to do with CoreGraphics -- basically, you need to negate your path and draw a drop shadow below it, clipped to your original path.
You can take a look at PaintCode and it will show you the code. It has a 15-min demo mode if you don't want to purchase it, that should be enough for your needs.
You could try this:
#import <QuartzCore/QuartzCore.h>
and in your code , after making the your view set these:
self.layer.cornerRadius = x;
self.layer.masksToBounds = TRUE;
This allows you to have rounded corners on your view. And if you calculate the radius to match your view , you should get the desired look.
- (id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
self.backgroundColor = [UIColor grayColor];
}
return self;
}
- (void)drawRect:(CGRect)rect
{
CGContextRef context =UIGraphicsGetCurrentContext();
CGContextSetRGBStrokeColor(context, 1.0, 1.0, 1.0, 1.0);
// And draw with a blue fill color
CGContextSetRGBFillColor(context, 0.0, 0.0, 1.0, 1.0);
// Draw them with a 2.0 stroke width so they are a bit more visible.
CGContextSetLineWidth(context, 2.0);
CGContextAddRect(context, self.bounds);
CGContextStrokePath(context);
// Close the path
CGContextClosePath(context);
// Fill & stroke the path
CGContextDrawPath(context, kCGPathFillStroke);
self.layer.cornerRadius = self.bounds.size.width/12;
self.layer.masksToBounds = TRUE;
}
I think it will be helpful to you.
Try the below code where myView is the UIView to which you want to set the shadow.
myView.layer.cornerRadius = 5.0f;
myView.layer.masksToBounds = YES;
[myView.layer setShadowColor:[[UIColor blackColor] colorWithAlphaComponent: 0.5]];
[myView.layer setShadowOffset:CGSizeMake(0, -1)];
Hope this helps.-

CAShapeLayer isn't rounding corners and stroke isn't on top

Hi guys I've creates this box, basically the corner radius isn't working & the stroke doesn't show on the top side of the shape either. Is there anyway to get this working?
UIBezierPath* conPath = [UIBezierPath bezierPath];
[conPath moveToPoint:CGPointMake(conX, conY)];
[conPath addLineToPoint:CGPointMake(conX, conY+50)];
[conPath addLineToPoint:CGPointMake(conX+220, conY+50)];
[conPath addLineToPoint:CGPointMake(conX+220, conY)];
CAShapeLayer* conLayer = [CAShapeLayer layer];
conLayer.path = conPath.CGPath;
conLayer.cornerRadius = 5.0;
UIColor *bg = [UIColor colorWithWhite:1 alpha:0.7];
[conLayer setFillColor:bg.CGColor];
[conLayer setStrokeColor:[UIColor grayColor].CGColor];
[[self layer] addSublayer:conLayer];

Irregularly shaped UIBezierPath filling

I'm making some custom control. One of the subviews of the control is transparent UIView with UIBezierPath path drawn on. On the one of the photos there is just let's say border of the UIBezierPath (there's called [path stroke]), but on the second one you can see what happens when I call [path fill]. I'd like the path filled to create shape similar to the one on the first photo. drawRect method from this transparent UIView is below.
- (void)drawRect:(CGRect)rect
{
// Drawing code
[super drawRect:rect];
UIBezierPath *path;
[[UIColor greenColor] setStroke];
[[UIColor greenColor] setFill];
//Angles
CGFloat startAngle = degreesToRadians(225);
CGFloat endAngle = degreesToRadians(315);
CGPoint center = CGPointMake(CGRectGetMidX(rect), lRadius);
path = [UIBezierPath bezierPathWithArcCenter: center radius:lRadius startAngle:startAngle endAngle:endAngle clockwise:YES];
rightEndOfLine = path.currentPoint;
bigArcHeight = rightEndOfLine.y;
CGFloat smallArcHeight = lRadius - bigArcHeight - sRadius;
CGFloat smallArcWidthSquare = (8*smallArcHeight*sRadius) - (4 * (smallArcHeight * smallArcHeight));
smallArcWidth = sqrtf(smallArcWidthSquare);
leftStartOfLine = CGPointMake((rect.size.width - smallArcWidth)/2, lRadius-sRadius + smallArcHeight);
CGFloat lengthOfSpace = (rect.size.width - rightEndOfLine.x);
[path moveToPoint:leftStartOfLine];
[path addArcWithCenter:center radius:sRadius startAngle:startAngle endAngle:endAngle clockwise:YES];
rightStartOfLine = path.currentPoint;
leftEndOfLine = CGPointMake(lengthOfSpace, bigArcHeight);
[path moveToPoint:rightStartOfLine];
[path addLineToPoint:rightEndOfLine];
[path moveToPoint:leftStartOfLine];
[path addLineToPoint:leftEndOfLine];
[path closePath];
[path stroke];
// [path fill];
}
Thanks for help !
Try to remove all of your moveToPoint calls. This is a continuous shape so they are not required, and they are confusing the filling algorithm. You can start at the top left corner, draw the arc clockwise, draw the segment (?) line, draw the arc anticlockwise, then draw the last segment line (or close the path). This will fill properly.