I would like to draw one big circle and place some smaller circles as shown in the image below
I draw the big circe in - (void)drawRect:(CGRect)rect
CGFloat rectX = self.frame.size.width / 2;
CGFloat rectY = self.frame.size.height / 2;
CGFloat width = self.frame.size.width-30;
CGFloat height = self.frame.size.width -30;
CGFloat centerX = rectX - width/2;
CGFloat centerY = rectY - height/2;
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(centerX, centerY, width, height)];
[[UIColor blackColor] set];
[bezierPath stroke];
Lets say I want to find 10 equally spaced points on the circle in order to draw 10 smaller red circles. Is there any smart solution? Thank you in advance.
The equation for a circle is:
x = cx + r * cos(a)
y = cy + r * sin(a)
where r is the radius, (cx, cy) the origin, and a the angle
you can draw a circle with
(UIBezierPath *)bezierPathWithArcCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise
function by using a CGPoint as centre and some value as the radius. You can give start angle and endangle as 0 and 360 for drawing the circle. Choose appropriate radius for the small circle and find the points using the equation mentioned at start and draw the circle
Our designer has asked me to recreate this:
Im subclassing UIView, and I've overridden the drawRect command like this:
[super drawRect:frame];
CGFloat x = self.frame.origin.x;
CGFloat y = self.frame.origin.y;
CGFloat w = self.frame.size.width;
CGFloat h = self.frame.size.height;
CGFloat lineWidth = lineWidthRequested;
CGPoint centerPoint = CGPointMake(w/2, h/2);
CGFloat radius = radiusRequested;
CGFloat startAngle = 3 * M_PI / 2;
CGFloat endAngle = startAngle + percentage * 2 * M_PI;
CGMutablePathRef arc = CGPathCreateMutable();
CGPathAddArc(arc, NULL,
centerPoint.x, centerPoint.y,
radius,
startAngle,
endAngle,
NO);
CGPathRef strokedArc = CGPathCreateCopyByStrokingPath(arc, NULL,
lineWidth,
kCGLineCapButt,
kCGLineJoinMiter, // the default
10); // 10 is default miter limit
CGContextRef c = UIGraphicsGetCurrentContext();
CGContextAddPath(c, strokedArc);
CGContextSetFillColorWithColor(c, [UIColor colorWithRed:239/255.0 green:101/255.0 blue:47/255.0 alpha:1.0].CGColor);
CGContextDrawPath(c, kCGPathFill);
What I ended up with is this:
Gotta still draw the arrowhead. Gonna be easy, right?
After struggling to remember my trig, I found rotation of points around a center on this page:
Rotating a point around an origin in VB
But when I tried translation to objective C to draw the arrowhead, I'm getting very odd results. Here's the code further down in drawRect:
CGFloat triangle[3][2] = {{centerPoint.x + 10, h - (centerPoint.y + radius)},
{centerPoint.x, h - (centerPoint.y + radius + lineWidth/2)},
{centerPoint.x, h - (centerPoint.y + radius - lineWidth/2)}};
for (int idx=0;idx < 3; idx++) {
// translate to origin
triangle[idx][0] -= centerPoint.x;
triangle[idx][1] -= centerPoint.y;
}
CGFloat angDistance = endAngle - startAngle;
CGFloat ct = cos(angDistance);
CGFloat st = sin(angDistance);
for (int idx=0;idx < 3; idx++) {
// rotate
triangle[idx][0] = ct * triangle[idx][0] - st * triangle[idx][1];
triangle[idx][1] = -st * triangle[idx][0] + ct * triangle[idx][1];
}
for (int idx=0;idx < 3; idx++) {
// translate back to position
triangle[idx][0] += centerPoint.x;
triangle[idx][1] += centerPoint.y;
}
NSLog(#"Rotating through %g, %06.1f,%06.1f , ct - %g, st - %g",angDistance, triangle[0][0],triangle[0][1],ct, st);
// XXX todo draw the filled triangle at end.
// draw a red triangle, the point of the arrow
CGContextSetFillColorWithColor(c, [[UIColor greenColor] CGColor]);
CGContextMoveToPoint(c, triangle[0][0], triangle[0][1]);
CGContextAddLineToPoint(c, triangle[1][0], triangle[1][1]);
CGContextAddLineToPoint(c, triangle[2][0], triangle[2][1]);
CGContextFillPath(c);
I was expecting that I make these points, then translate them to an origin, rotate, and then translate them back, I'd be laughing.
However, that's not what's happening...as the percentage increases from 0 to 2pi, the arrowhead draws itself in a vaguely triangular route. When the angDistance is zero or pi, the arrowhead is in the right place. As I head towards pi/2 or 3pi/2 though, the arrowhead heads off towards the lower corners of an enclosing rect.
I must be doing something blatantly stupid, but I can't for the life of me see it.
Any ideas?
Thanks,
-Ken
I'd suggest constructing a path for the entire outline of the desired shape and then "fill" that path with the desired color. That eliminates any risk of any gaps or anything not quite lining up.
Thus, this path might consisting of an arc for the outside of the arrow, two lines for the head of the arrow, an arc back for the inside of the arrow, and then close the path. That might look like:
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetFillColorWithColor(context, self.arrowColor.CGColor);
CGPoint center = CGPointMake(rect.size.width / 2.0, rect.size.height / 2.0);
CGContextMoveToPoint(context, center.x + cosf(self.startAngle) * (self.radius + self.lineWidth / 2.0),
center.y + sinf(self.startAngle) * (self.radius + self.lineWidth / 2.0));
CGContextAddArc(context, center.x, center.y, self.radius + self.lineWidth / 2.0, self.startAngle, self.endAngle, !self.clockwise);
CGFloat theta = asinf(self.lineWidth / self.radius / 2.0) * (self.clockwise ? 1.0 : -1.0);
CGFloat pointDistance = self.radius / cosf(theta);
CGContextAddLineToPoint(context, center.x + cosf(self.endAngle + theta) * pointDistance,
center.y + sinf(self.endAngle + theta) * pointDistance);
CGContextAddLineToPoint(context, center.x + cosf(self.endAngle) * (self.radius - self.lineWidth / 2.0),
center.y + sinf(self.endAngle) * (self.radius - self.lineWidth / 2.0));
CGContextAddArc(context, center.x, center.y, self.radius - self.lineWidth / 2.0, self.endAngle, self.startAngle, self.clockwise);
CGContextClosePath(context);
CGContextDrawPath(context, kCGPathFill);
}
The only trick here was coming up with the right point for the end of the arrow. I've improved the choice to handle fatter arrows a little better, but you should feel free to use whatever you feel is best for your application.
Thus, the following code:
self.arrowView.radius = 100;
self.arrowView.arrowColor = [UIColor blueColor];
self.arrowView.lineWidth = 40;
self.arrowView.startAngle = -M_PI_2;
self.arrowView.endAngle = M_PI;
self.arrowView.clockwise = TRUE;
would yield the following (which I'm animating with a CADisplayLink):
This uses the start angle of zero as meaning the "3 o'clock" position, but you can obviously tweak this as you see fit. But hopefully it illustrates one approach to the problem.
By the way, while I've answered the question of how to do this with CoreGraphics, I wouldn't necessarily suggest doing so. For example, in https://github.com/robertmryan/CircularArrowDemo, I don't implement drawRect, but instead update a CAShapeLayer. By doing this, not only do I avoid drawRect inefficiencies, but one could theoretically also change how you use this CAShapeLayer (e.g. use it as a mask for some UIView, revealing some more interesting color gradation (or other image) behind it).
Here is another solution (not very scalable though). This solution assumes this is like a logo where the angle/percent of circle drawn will not change.
- (void)drawRect:(CGRect)rect {
UIBezierPath *circleOutline = [UIBezierPath bezierPath];
[self.circleColor setStroke];
[circleOutline setLineWidth:self.bounds.size.width*0.15];
[circleOutline addArcWithCenter:CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2) radius:self.bounds.size.width/2-circleOutline.lineWidth/2 startAngle:3*M_PI/2 endAngle:3*M_PI/4 clockwise:YES];
[circleOutline stroke];
[self addArrowView:circleOutline];
}
- (void)addArrowView:(UIBezierPath *)path {
for (int x = 0; x < self.bounds.size.width/2; x++) {
for (int y = self.bounds.size.height/2; y < self.bounds.size.height; y++) {
if ([path containsPoint:CGPointMake(x, y)]) {
// Pythagorean Theorem - We want the diagonal length of the square to be lineWidth, so we need to calculate what size
// to make each side of the square to make the diagonal equal to lineWidth
double sideLength = sqrt((path.lineWidth*path.lineWidth)/2);
UIView *arrowView = [[UIView alloc] initWithFrame:CGRectMake(x-sideLength/2, y-sideLength/2, sideLength, sideLength)];
arrowView.backgroundColor = self.circleColor;
[self addSubview:arrowView];
return;
}
}
}
}
would yield:
I'm trying to animate an parallelogram bezier path. But as the size is changed the angles are disproportionately changed. How could I do it to maintain the same shape and the same angle as the first (magenta) ?.
Here is the code and images for you to understand my problem:
- (UIBezierPath *) createPathRisedRect:(CGRect) rect
{
UIBezierPath *path = [[UIBezierPath alloc] init];
const CGFloat RISE = 10;
CGPoint pt0 = CGPointMake(CGRectGetMinX(rect), CGRectGetMinY(rect));
CGPoint pt1 = CGPointMake(CGRectGetMinX(rect) + RISE, CGRectGetMaxY(rect));
CGPoint pt2 = CGPointMake(CGRectGetMaxX(rect) + RISE, CGRectGetMaxY(rect));
CGPoint pt3 = CGPointMake(CGRectGetMaxX(rect), CGRectGetMinY(rect));
CGPoint pt4 = CGPointMake(CGRectGetMinX(rect), CGRectGetMinY(rect));
[path moveToPoint:pt0];
[path addLineToPoint:pt1];
[path addLineToPoint:pt2];
[path addLineToPoint:pt3];
[path addLineToPoint:pt4];
return path;
}
i have a situation where the user will tap on the screen and enters the degrees and a line should to be draw from the tapped point in the given degree.
i am using the following path code to draw the line but i am not able to draw the line in the correct degree.
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path,NULL,touchx,touchy);
CGPathAddLineToPoint(path, NULL,endX, endY);
lineLayer = [CAShapeLayer layer];
[lineLayer setPath:path];
[lineLayer setFillColor:[[UIColor lightTextColor] CGColor]];
[lineLayer setStrokeColor:[[UIColor blackColor] CGColor]];
[lineLayer setAnchorPoint:CGPointMake(0.0f, 0.0f)];
[lineLayer setPosition:CGPointMake(0.0f, 0.0f)];
[drawingview.layer addSublayer:lineLayer];
CGPathRelease(path);
You posted code that creates a path with a start point, and and end point, but did not post the most important bit: how you calculate the end point.
I assume that's the part you need help with.
You need to use basic trigonometry. Trig on iOS is based on radians, so you will need to convert degrees to radians:
CGFloat radians = M_PI * degrees / 180.0;
Then you need to calculate your end point based on your start point.
Remember the mnemonic of the Indian chief "SOH CAH TOA" (sine = opposite/hypotenuse, cosine = adjacent/hypotenuse, tangent = opposite/adjacent).
Drawing a diagram of a right triangle with the angle in question on the left and the right angle on the right:
The X measurement is the adjacent side.
So, if cosine = adjacent/hypotenuse, then adjacent = cos * hypotenuse.
The hypotenuse of the triangle is the radius of the circle. Replacing variables:
x = cos(angle) * radius
The y measurement is the opposite side.
If sine = opposite/hypotenuse, then opposite = sine * hypotenuse.
The hypotenuse of the triangle is the radius of the circle. Replacing variables:
y = sin(angle) * radius
So your code to calculate the end-point might look like this:
CGFloat radius = 50; //Picked out of thin air; decide on a radius value
CGFloat deltaX = radius * cosf(radians);
CGFloat deltaY = radius * sinf(radians);
endX = touchX+deltaX;
endy = touchY+deltaY;
I want to draw an arc like in the following figure using core draw.
Not exactly same but I have background images set. I need to draw arc based on the file download. Now the percentage of download number I have. How can I do it using core draw?
The image above are just two circles - one is cutting a hole in another. This is not near to what you want to accomplish.
You need to use CGContextAddArc.
Do something like this:
open a path
move to a point
start drawing the Arc with CGContextAddArc
move (and draw a line) inwards to the arc center the desired width of the slice
draw an backward arc
close the path
Use Mid point Algorithm, If you want to draw circles , to make concentric circle provide difference in radius of your circles
I have what you want:
// CircularProgress.h
#import <UIKit/UIKit.h>
#interface CircularProgress : UIView
#property (nonatomic, assign) CGFloat percent;
#end
// CircularProgress.m
#import "CircularProgress.h"
#implementation CircularProgress
- (void)setPercent:(CGFloat)percent
{
_percent = percent;
[self setNeedsDisplay];
}
- (void)drawRect:(CGRect)rect
{
[super drawRect:rect];
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect bounds = [self bounds];
CGPoint center = CGPointMake(bounds.size.width / 2.0, bounds.size.height / 2.0);
CGFloat lineWidth = 8.0;
CGFloat innerRadius = (bounds.size.width / 2.0) - lineWidth;
CGFloat outerRadius = innerRadius + lineWidth;
CGFloat startAngle = -((float)M_PI / 2.0);
CGFloat endAngle = ((self.percent / 100.0) * 2 * (float)M_PI) + startAngle;
UIBezierPath *processBackgroundPath = [UIBezierPath bezierPath];
processBackgroundPath.lineWidth = lineWidth;
CGFloat radius = (self.bounds.size.width - lineWidth) / 2.0;
CGFloat fullAngle = (2.0 * (float)M_PI) + startAngle;
[processBackgroundPath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:fullAngle clockwise:YES];
[[UIColor whiteColor] set];
[processBackgroundPath stroke];
CGMutablePathRef progressPath = CGPathCreateMutable();
CGPathMoveToPoint(progressPath, NULL, center.x, center.y - innerRadius);
CGPathAddArc(progressPath, NULL, center.x, center.y, innerRadius, startAngle, endAngle, YES);
CGPathAddArc(progressPath, NULL, center.x, center.y, outerRadius, endAngle, startAngle, NO);
CGPathCloseSubpath(progressPath);
UIColor *aColor = [UIColor colorWithRed:0.941 green:0.776 blue:0.216 alpha:1.0];
[aColor setFill];
CGContextAddPath(context, progressPath);
CGContextFillPath(context);
CGPathRelease(progressPath);
}
#end
You just need to create an object of CircularProgress class of the desired size (it should be square) and add it as subview to your main view. Then simply change the percent value and enjoy result. The color and width are hardcoded for now because it's not the finished control but you should catch the idea.