how to draw an arc with line using UIBezierPath - ios

I am trying to draw shape shown in figure. Background is white.. Hope it is visible to you..
I am using bezier path to draw this. I have provided bounds to shape as shown by blue border.
So far I am successfully able to draw just two lines(shown in green). I have to draw the one with red further.
I am unable to draw arc from this point. I can't understand how to pass correct parameters to addArcWithCenter.
Code
-(void) drawRect:(CGRect)rect
{
//declare and instantiate the UIBezierPath object
aPath = [UIBezierPath bezierPath];
// Set the starting point of the shape.
[aPath moveToPoint:CGPointMake(CGRectGetMinX(rect), CGRectGetMinY(rect))];
// Draw some lines.
[aPath addLineToPoint:CGPointMake(CGRectGetMaxX(rect), CGRectGetMinY(rect))];
[aPath addLineToPoint:CGPointMake(CGRectGetMaxX(rect), CGRectGetMaxY(rect) - 40)];
[aPath addArcWithCenter:self.center radius:40 startAngle:3 *(M_PI / 2) endAngle:M_PI clockwise:NO];
//set the line width
aPath.lineWidth = 2;
//set the stoke color
[[UIColor greenColor] setStroke];
//draw the path
[aPath stroke];
}
I am new to core graphics. Please be lenient over me.. Thanks..

Try with this (as you can see, I've used addQuadCurveToPoint a variant of addCurveToPoint proposed by #wain - ask google for addCurveToPoint and switch to picture search to see how it work ) :
-(void) drawRect:(CGRect)rect
{
UIBezierPath * aPath = [UIBezierPath bezierPath];
// Set the starting point of the shape.
[aPath moveToPoint:CGPointMake(CGRectGetMinX(rect), CGRectGetMinY(rect))];
// Draw some lines.
[aPath addLineToPoint:CGPointMake(CGRectGetMaxX(rect), CGRectGetMinY(rect))];
//changes start here !
//the point look to be at 80% down
[aPath addLineToPoint:CGPointMake(CGRectGetMaxX(rect), CGRectGetMaxY(rect) * .8)];
//1st arc
//The end point look to be at 1/4 at left, bottom
CGPoint p = CGPointMake(CGRectGetMaxX(rect) / 4, CGRectGetMaxY(rect));
CGPoint cp = CGPointMake( (CGRectGetMaxX(rect) / 4) + ((CGRectGetMaxX(rect) - (CGRectGetMaxX(rect) / 4)) / 2) , CGRectGetMaxY(rect) * .8);
[aPath addQuadCurveToPoint:p controlPoint:cp];
//2nd arc
//The end point look to be at 80% downt at left,
CGPoint p2 = CGPointMake(CGRectGetMinX(rect), CGRectGetMaxY(rect) * .8);
CGPoint cp2 = CGPointMake( (CGRectGetMaxX(rect) / 4) / 2 , CGRectGetMaxY(rect) * .8);
[aPath addQuadCurveToPoint:p2 controlPoint:cp2];
//close the path
[aPath closePath];
//set the line width
aPath.lineWidth = 2;
//set the stoke color
[[UIColor greenColor] setStroke];
//draw the path
[aPath stroke];
}

Related

Draw VU meter using Core Graphics in iOS

I'm trying to draw a somewhat similar image to this, using Core Graphics:
I was able to draw the main arc, but I am not able to understand, how to divide arc into parts/draw graduation on arc? My current code to draw the arc is:
[path addArcWithCenter:point radius:radius startAngle:DEGREES_TO_RADIANS(specific_start_angle) endAngle:DEGREES_TO_RADIANS(specific_end_angle) clockwise:NO];
I tried using strokeWithBlendMode but I am facing problem with position of graduations or ticks.
Teja's solution will work fine for you, but it does require that you calculate your own start and end points for the graduations.
I suggest you create a function that let's you draw the graduations at a given angle of the arc, that will calculate the start and end points of the graduations, given a length.
static inline void drawGraduation(CGPoint center, CGFloat radius, CGFloat angle, CGFloat length, CGFloat width, CGColorRef color) {
CGContextRef c = UIGraphicsGetCurrentContext();
CGFloat radius2 = radius+length; // The radius of the end points of the graduations
CGPoint p1 = (CGPoint){cos(angle)*radius+center.x, sin(angle)*radius+center.y}; // the start point of the graduation
CGPoint p2 = (CGPoint){cos(angle)*radius2+center.x, sin(angle)*radius2+center.y}; // the end point of the graduation
CGContextMoveToPoint(c, p1.x, p1.y);
CGContextAddLineToPoint(c, p2.x, p2.y);
CGContextSetStrokeColorWithColor(c, color);
CGContextSetLineWidth(c, width);
CGContextStrokePath(c);
}
You can then call this in a for loop (or however you want to do it) when you draw your arc for the main scale of your VU meter. You can also easily customise the color, width and length of given graduations at given intervals (for example, this code gives a thicker & longer red line every 5 graduations).
- (void)drawRect:(CGRect)rect {
CGRect r = self.bounds;
CGFloat startAngle = -M_PI*0.2; // start angle of the main arc
CGFloat endAngle = -M_PI*0.8; // end angle of the main arc
NSUInteger numberOfGraduations = 16;
CGPoint center = (CGPoint){r.size.width*0.5, r.size.height*0.5}; // center of arc
CGFloat radius = (r.size.width*0.5)-20; // radius of arc
CGFloat maxGraduationWidth = 1.5; // the maximum graduation width
CGFloat maxGraduationWidthAngle = maxGraduationWidth/radius; // the maximum graduation width angle (used to prevent the graduations from being stroked outside of the main arc)
// draw graduations
CGFloat deltaArc = (endAngle-startAngle+maxGraduationWidthAngle)/(numberOfGraduations-1); // the change in angle of the arc
CGFloat startArc = startAngle-(maxGraduationWidthAngle*0.5); // the starting angle of the arc
for (int i = 0; i < numberOfGraduations; i++) {
if (i % 5 == 0) {
drawGraduation(center, radius, startArc+(i*deltaArc), 14, 1.5, [UIColor redColor].CGColor); // red graduation every 5 graduations.
} else {
drawGraduation(center, radius, startArc+(i*deltaArc), 10, 1, [UIColor blackColor].CGColor);
}
}
// draw main arc
UIBezierPath* mainArc = [UIBezierPath bezierPathWithArcCenter:center radius:radius startAngle:startAngle endAngle:endAngle clockwise:NO];
[[UIColor blackColor] setStroke];
mainArc.lineWidth = 2;
[mainArc stroke];
}
Output
Full project: https://github.com/hamishknight/VU-Meter-Arc
You can draw bezier path with dashes ,something like this::
//// Bezier Drawing
UIBezierPath* bezierPath = [UIBezierPath bezierPath];
[bezierPath moveToPoint: CGPointMake(54.5, 62.5)];
[bezierPath addCurveToPoint: CGPointMake(121.5, 39.5) controlPoint1: CGPointMake(54.5, 62.5) controlPoint2: CGPointMake(87.5, 39.5)];
[bezierPath addCurveToPoint: CGPointMake(190.5, 62.5) controlPoint1: CGPointMake(155.5, 39.5) controlPoint2: CGPointMake(190.5, 62.5)];
[UIColor.blackColor setStroke];
bezierPath.lineWidth = 10;
CGFloat bezierPattern[] = {24, 2};
[bezierPath setLineDash: bezierPattern count: 2 phase: 0];
[bezierPath stroke];
Else You can draw multiple bezier path and draw it similar to what you want:
Something like this:
For the small line:
UIBezierPath* bezier2Path = [UIBezierPath bezierPath];
[bezier2Path moveToPoint: CGPointMake(65, 45)];
[bezier2Path addLineToPoint: CGPointMake(75, 63)];
[UIColor.blackColor setStroke];
bezier2Path.lineWidth = 1.5;
[bezier2Path stroke];

Fill UIBezierPath with parallel lines

I'm trying to draw custom shape using UIBezierPath:
UIBezierPath *aPath = [UIBezierPath bezierPath];
[aPath moveToPoint:CGPointMake(100.0, 0.0)];
// Draw the lines.
[aPath addLineToPoint:CGPointMake(200.0, 40.0)];
[aPath addLineToPoint:CGPointMake(160, 140)];
[aPath addLineToPoint:CGPointMake(40.0, 140)];
[aPath addLineToPoint:CGPointMake(0.0, 40.0)];
[aPath closePath];
I want to fill it with parallel lines to make this stripped. I want to change color of this lines too.
Assume that I want to make them vertical.
I have to calculate somehow points on this path with regular interval, how I can do this?
I have found this UIColor colorWithPatternImage but then i cant change color and "density" of my lines inside shape.
Like Nikolai Ruhe said, the best option is to use your shape as the clipping path, and then draw some pattern inside the bounding box of the shape. Here's an example of what the code would look like
- (void)drawRect:(CGRect)rect
{
// create a UIBezierPath for the outline shape
UIBezierPath *aPath = [UIBezierPath bezierPath];
[aPath moveToPoint:CGPointMake(100.0, 0.0)];
[aPath addLineToPoint:CGPointMake(200.0, 40.0)];
[aPath addLineToPoint:CGPointMake(160, 140)];
[aPath addLineToPoint:CGPointMake(40.0, 140)];
[aPath addLineToPoint:CGPointMake(0.0, 40.0)];
[aPath closePath];
[aPath setLineWidth:10];
// get the bounding rectangle for the outline shape
CGRect bounds = aPath.bounds;
// create a UIBezierPath for the fill pattern
UIBezierPath *stripes = [UIBezierPath bezierPath];
for ( int x = 0; x < bounds.size.width; x += 20 )
{
[stripes moveToPoint:CGPointMake( bounds.origin.x + x, bounds.origin.y )];
[stripes addLineToPoint:CGPointMake( bounds.origin.x + x, bounds.origin.y + bounds.size.height )];
}
[stripes setLineWidth:10];
CGContextRef context = UIGraphicsGetCurrentContext();
// draw the fill pattern first, using the outline to clip
CGContextSaveGState( context ); // save the graphics state
[aPath addClip]; // use the outline as the clipping path
[[UIColor blueColor] set]; // blue color for vertical stripes
[stripes stroke]; // draw the stripes
CGContextRestoreGState( context ); // restore the graphics state, removes the clipping path
// draw the outline of the shape
[[UIColor greenColor] set]; // green color for the outline
[aPath stroke]; // draw the outline
}
Using Swift
override func draw(_ rect: CGRect){
// create a UIBezierPath for the outline shape
let aPath = UIBezierPath()
aPath.move(to: CGPoint(x: 100.0, y: 0.0))
aPath.addLine(to: CGPoint(x: 200.0, y: 40.0))
aPath.addLine(to: CGPoint(x: 160, y: 140))
aPath.addLine(to: CGPoint(x: 40.0, y: 140))
aPath.addLine(to: CGPoint(x: 0.0, y: 40.0))
aPath.close()
aPath.lineWidth = 10
// get the bounding rectangle for the outline shape
let bounds = aPath.bounds
// create a UIBezierPath for the fill pattern
let stripes = UIBezierPath()
for x in stride(from: 0, to: bounds.size.width, by: 20){
stripes.move(to: CGPoint(x: bounds.origin.x + x, y: bounds.origin.y ))
stripes.addLine(to: CGPoint(x: bounds.origin.x + x, y: bounds.origin.y + bounds.size.height ))
}
stripes.lineWidth = 10
let context = UIGraphicsGetCurrentContext()
// draw the fill pattern first, using the outline to clip
context!.saveGState() // save the graphics state
aPath.addClip() // use the outline as the clipping path
UIColor.blue.set() // blue color for vertical stripes
stripes.stroke() // draw the stripes
context!.restoreGState() // restore the graphics state, removes the clipping path
// draw the outline of the shape
UIColor.green.set() // green color for the outline
aPath.stroke() // draw the outline
}
Produces
The best option is to set the original shape that you want to draw as a clipping path (CGContextClip) on the context.
Now, just draw all the parallel lines into the bounding box of the shape. You are free to vary the color or distance of the lines, then.
I don't have code for this at the moment but you are going down the right path with [UIColor colorWithPatternImage:....
However, if you want to make this more flexible you could generate the image programatically.
You only need a very small image of a few pixels. Enough for one of each colour of stripe.
If your stripes are green and red and the green is 4 pixels wide and the red 3 pixels wide then you only need an image that is 1 pixel wide and 7 pixels tall.
Then use this automatically generated image as the pattern image for the colour.
That's probably the easiest way I can think of.
Generating an image is not very hard. There are many SO questions about drawing into an image context.

UIBezierPath bezierPathWithArcCenter with degree lines

i am creating a circle with
+(UIBezierPath *)circleShape:(CGRect)originalFrame{
CGRect frame = [self maximumSquareFrameThatFits:originalFrame];
UIBezierPath *aPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(160, 150)
radius:100
startAngle:0
endAngle:DEGREES_TO_RADIANS(360)
clockwise:YES];
return aPath;
}
I want to add 2 lines vertical and horizontal through the center and extend past the bounds of the clircle
I have tried addLineToPoint but i get nothing in the circle
TIA
Thanks, im adding lines like so and apart from playing with positioning the line extending past the bounds of the circle are displaying but im guessing the lines in the circle are the same color as the circle, so not visible. Do i need to create the lines in another method then use appendPath? so i can assign a different color.
UIBezierPath *aPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(160, 150)
radius:100
startAngle:0
endAngle:DEGREES_TO_RADIANS(360)
clockwise:YES];
[aPath moveToPoint: CGPointMake(CGRectGetMinX(frame)
+ 0.30000 * CGRectGetWidth(frame),CGRectGetMinY(frame) + 0.15000
* CGRectGetHeight(frame))];
[aPath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 0.27634
* CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.10729 * CGRectGetHeight(frame))];
[aPath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 0.97553 *
CGRectGetWidth(frame), CGRectGetMinY(frame) + 0.39549 * CGRectGetHeight(frame))];
[aPath closePath];
return aPath;
After you draw the circle, the current graphics location is the last point on the circle. If you call addLineToPoint, the line is drawn from the last point on the circle to the end point that you pass to addLineToPoint.
To start a line that's not attached to the endpoint of the circle, use moveToPoint to change the current graphics location.
Note that if you stroke the path and then fill it, then the lines inside the circle will not be shown. That's because the fill operation will overwrite any pixels that were drawn inside the circle. If you fill the path first, and then stroke it, then the lines will be drawn on top of the circle (provided that the stroke and fill colors are different).

Drawing a bezier curve between a set of given points

What is the best way to draw a bezier curve, in iOS application, that passes through a set of given points
A little more generic way to do it can be achieved by, for example, looking at the BEMSimpleLineGraph GitHub Project (see here for more info: bemsimplelinegraph). Here I extracted a method to draw a bezier curve through a given list of points.
The header file (BezierLine.h):
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <CoreGraphics/CoreGraphics.h>
#interface BezierLine : NSObject
/*
Draws a bezier curved line on the given context
with points: Array of CGPoint values
*/
-(void) drawBezierCurveInContext:(CGContextRef)context withPoints:(NSArray*)points lineColor:(UIColor*)color lineWidth:(CGFloat)lineWidth;
#end
The implementation (BezierLine.m):
#import "BezierLine.h"
#implementation BezierLine
-(void) drawBezierCurveInContext:(CGContextRef)context withPoints:(NSArray*)points lineColor:(UIColor*)color lineWidth:(CGFloat)lineWidth {
if (points.count < 2) return;
CGPoint CP1;
CGPoint CP2;
// LINE
UIBezierPath *line = [UIBezierPath bezierPath];
CGPoint p0;
CGPoint p1;
CGPoint p2;
CGPoint p3;
CGFloat tensionBezier1 = 0.3;
CGFloat tensionBezier2 = 0.3;
CGPoint previousPoint1;
CGPoint previousPoint2;
[line moveToPoint:[[points objectAtIndex:0] CGPointValue]];
for (int i = 0; i < points.count - 1; i++) {
p1 = [[points objectAtIndex:i] CGPointValue];
p2 = [[points objectAtIndex:i + 1] CGPointValue];
const CGFloat maxTension = 1.0f / 3.0f;
tensionBezier1 = maxTension;
tensionBezier2 = maxTension;
if (i > 0) { // Exception for first line because there is no previous point
p0 = previousPoint1;
if (p2.y - p1.y == p1.y - p0.y) tensionBezier1 = 0;
} else {
tensionBezier1 = 0;
p0 = p1;
}
if (i < points.count - 2) { // Exception for last line because there is no next point
p3 = [[points objectAtIndex:i + 2] CGPointValue];
if (p3.y - p2.y == p2.y - p1.y) tensionBezier2 = 0;
} else {
p3 = p2;
tensionBezier2 = 0;
}
// The tension should never exceed 0.3
if (tensionBezier1 > maxTension) tensionBezier1 = maxTension;
if (tensionBezier2 > maxTension) tensionBezier2 = maxTension;
// First control point
CP1 = CGPointMake(p1.x + (p2.x - p1.x)/3,
p1.y - (p1.y - p2.y)/3 - (p0.y - p1.y)*tensionBezier1);
// Second control point
CP2 = CGPointMake(p1.x + 2*(p2.x - p1.x)/3,
(p1.y - 2*(p1.y - p2.y)/3) + (p2.y - p3.y)*tensionBezier2);
[line addCurveToPoint:p2 controlPoint1:CP1 controlPoint2:CP2];
previousPoint1 = p1;
previousPoint2 = p2;
}
CGContextSetAllowsAntialiasing(context, YES);
CGContextSetStrokeColorWithColor(context, color.CGColor);
CGContextSetLineWidth(context, lineWidth);
CGContextAddPath(context, line.CGPath);
CGContextDrawPath(context, kCGPathStroke);
}
#end
You can use it by for example creating an image context using UIGraphicsBeginImageContext and retrieving the context with UIGraphicsGetCurrentContext().
Otherwise you may want to change the code and assign the resulting Path to a CALayer and add that to an UIView.
Hope this helps.
I know this might be late, but just for anyone who is looking for the right answer. Instead of using addLineToPoint to draw the straight line. You can use addCurveToPoint to draw the curve. e.g.
[bezierPath moveToPoint:CGPointMake(0, 0)];
[bezierPath addCurveToPoint:CGPointMake(40, 100)
controlPoint1:CGPointMake(20, 0)
controlPoint2:CGPointMake(20, 100)];
[bezierPath addCurveToPoint:CGPointMake(80, 50)
controlPoint1:CGPointMake(60, 100)
controlPoint2:CGPointMake(60, 50)];
// and you may don't want to close the path
// [bezierPath closePath];
It's really up to you to choose the control points of the curve. I just use the x = last_point_x + 20; y = last_point_y for control point one, and x = current_point_x - 20; y = current_point_y;
and you may want to use other value instead of the 20 as you may have different segment width of the curve.
You can easily google some example of how to create bezier curve on the web. I found this short tut as an example.
You can create a close bezier curve for e.g. with the following code snippet:
UIBezierPath* path = [UIBezierPath bezierPath];
[path moveToPoint:pt1];
[path addLineToPoint:pt2];
[path addLineToPoint:pt3];
[path closePath];
I hope it will help as a starting point.
Please try this.
UIImageView *waterLevel = [[UIImageView alloc] initWithFrame:CGRectMake(0,0,200,200)];
UIGraphicsBeginImageContext(waterLevel.frame.size);
[waterLevel.image drawAtPoint:CGPointZero];
//define BezierPath
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
// Set the starting point of the shape.
[bezierPath moveToPoint:CGPointMake(0, 0)];
[bezierPath addLineToPoint:CGPointMake(waterLevel.frame.size.width, 0)];
[bezierPath addLineToPoint:CGPointMake(waterLevel.frame.size.width, waterLevel.frame.size.height)];
[bezierPath addLineToPoint:CGPointMake(0, waterLevel.frame.size.height)];
[bezierPath closePath];
bezierPath.lineWidth = 15;
//set the stoke color
[[UIColor blackColor] setStroke];
//draw the path
[bezierPath stroke];
// Add to the current Graphic context
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextAddPath(context,bezierPath.CGPath);
waterLevel.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[self.view addSubview:waterLevel];
You can be much more efficient by using the CGPointFromString method:
NSArray *pointArray = #[#"{3.0,2.5}",#"{100.0,30.2}", #"{100.0,200.0}", #"{3.0,200.0}"];
// draw the path
UIBezierPath *aPath = [UIBezierPath bezierPath];
for (NSString *pointString in pointArray) {
if ([pointArray indexOfObject:pointString] == 0)
[aPath moveToPoint:CGPointFromString(pointString)];
else
[aPath addLineToPoint:CGPointFromString(pointString)];
}
[aPath closePath];
UIBezierPath *aPath = [UIBezierPath bezierPath];
// Set the starting point of the shape.
[aPath moveToPoint:CGPointMake(100.0, 0.0)];
// Draw the lines.
[aPath addLineToPoint:CGPointMake(200.0, 40.0)];
[aPath addLineToPoint:CGPointMake(160, 140)];
[aPath addLineToPoint:CGPointMake(40.0, 140)];
[aPath addLineToPoint:CGPointMake(0.0, 40.0)];
[aPath closePath];

UIBezierPath and applytransform

I am trying to implement a custom UIView which is basically a pie menu (something like a cake divided into slices).
For that I am trying to draw a circle and a series of lines from the center, like the rays in a chart's wheel.
I have successfully drawn the circle and and I would now like to draw the lines dividing the circle in slices.
This is what I have so far:
-(void)drawRect:(CGRect)rect{
[[UIColor blackColor] setStroke];
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGFloat minDim = (rect.size.width < rect.size.height) ? rect.size.width : rect.size.height;
CGRect circleRect = CGRectMake(0, rect.size.height/2-minDim/2, minDim, minDim);
CGContextAddEllipseInRect(ctx, circleRect);
CGContextSetFillColor(ctx, CGColorGetComponents([[UIColor yellowColor] CGColor]));
CGContextFillPath(ctx);
CGPoint start = CGPointMake(0, rect.size.height/2);
CGPoint end = CGPointMake(rect.size.width, rect.size.height/2);
for (int i = 0; i < MaxSlices(6); i++){
CGFloat degrees = 1.0*i*(180/MaxSlices(6));
CGAffineTransform rot = CGAffineTransformMakeRotation(degreesToRadians(degrees));
UIBezierPath *path = [self pathFrom:start to:end];
[path applyTransform:rot];
}
}
- (UIBezierPath *) pathFrom:(CGPoint) start to:(CGPoint) end{
UIBezierPath* aPath = [UIBezierPath bezierPath];
aPath.lineWidth = 5;
[aPath moveToPoint:start];
[aPath addLineToPoint:end];
[aPath closePath];
[aPath stroke];
return aPath;
}
The problem is that applyTransform on the path doesn't seem to do anything. The first path gest drawn correctly the and the following ones are unaffected by the rotation. Basically what I see is just one path. Check the screen shot here http://img837.imageshack.us/img837/9757/iossimulatorscreenshotf.png
Thank you for your help!
You're drawing the path (with stroke) before you transform it. A path is just a mathematical representation. It isn't the line "on the screen." You can't move what you've already drawn by modifying the data about it.
Just move the [aPath stroke] out of pathFrom:to: and put it after the applyTransform:.

Resources