Fill UIBezierPath with parallel lines - ios

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.

Related

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).

how to draw an arc with line using UIBezierPath

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];
}

Quartz and clipping area

I have a question about Quartz and clipping area:
I would like to have a rectangle A
inside this rectangle I would like to have a rectangle B
The filling of B dveve also cut in A: I would like that A was pierced by B. What is the best way to do this in quartz? I did not really understand how the clipping
If I understand you correctly, you want to draw a smaller rectangle inside a larger rectangle so that the inner rectangle is transparent. You can achieve this by drawing a CAShapeLayer with a path that contains both rectangles as subpaths. Don't forget to set the layer's fill rule to kCAFillRuleEvenOdd.
Try something like this:
CGRect rectA = CGRectMake(100, 100, 200, 200);
CGRect rectB = CGRectMake(150, 150, 100, 100);
UIBezierPath *path=[[UIBezierPath alloc] init];
// Add sub-path for rectA
[path moveToPoint:CGPointMake(rectA.origin.x, rectA.origin.y)];
[path addLineToPoint:CGPointMake(rectA.origin.x+rectA.size.width, rectA.origin.y)];
[path addLineToPoint:CGPointMake(rectA.origin.x+rectA.size.width, rectA.origin.y+rectA.size.height)];
[path addLineToPoint:CGPointMake(rectA.origin.x, rectA.origin.y+rectA.size.height)];
[path closePath];
// Add sub-path for rectB
[path moveToPoint:CGPointMake(rectB.origin.x, rectB.origin.y)];
[path addLineToPoint:CGPointMake(rectB.origin.x+rectB.size.width, rectB.origin.y)];
[path addLineToPoint:CGPointMake(rectB.origin.x+rectB.size.width, rectB.origin.y+rectB.size.height)];
[path addLineToPoint:CGPointMake(rectB.origin.x, rectB.origin.y+rectB.size.height)];
[path closePath];
// Create CAShapeLayer with this path
CAShapeLayer *pathLayer = [CAShapeLayer layer];
[pathLayer setFillRule:kCAFillRuleEvenOdd]; /* <- IMPORTANT! */
[pathLayer setPath:path.CGPath];
[pathLayer setFillColor:[UIColor blackColor].CGColor];
// Add the CAShapeLayer to a view
[someView.layer addSublayer:pathLayer];
I solved with this simple way:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetRGBFillColor(context, 0.0, 0.0, 0.0, 0);
CGContextFillRect(context,self.bounds);
CGContextAddRect(context, self.bounds);
//Add cropped rectangle:
CGContextAddRect(context, _croppedRegion);
//Clip:
CGContextEOClip(context);
CGContextSetRGBFillColor(context, 255.0, 255.0, 255.0, 0.5);
CGContextFillRect(context, self.bounds);
}

iOS draw filled Circles

Not a graphics programmer here, so I'm trying to stumble through this. I'm trying to draw 9 filled circles, each a different color, each with a white border. The UIView's frame is CGRectMake (0,0,60,60). See attached image.
The problem is I'm getting "flat spots" on the borders on each side. Following is my code (from the UIView subclass):
- (void)drawRect:(CGRect)rect
{
CGRect borderRect = CGRectMake(0.0, 0.0, 60.0, 60.0);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetRGBStrokeColor(context, 1.0, 1.0, 1.0, 1.0);
CGContextSetRGBFillColor(context, colorRed, colorGreen, colorBlue, 1.0);
CGContextSetLineWidth(context, 2.0);
CGContextFillEllipseInRect (context, borderRect);
CGContextStrokeEllipseInRect(context, borderRect);
CGContextFillPath(context);
}
If I change to CGRectMake(0,0,56,56) in drawRect, I get flat spots only on the top and left sides, and the bottom & right sides look fine.
Can anyone suggest how I might fix this? It seems to me the border is being clipped by the UIView, but not knowing much about this, I really don't know how to fix it.
Thanks, in advance, for any of you graphics experts' suggestions.
I like the answer from #AaronGolden, just wanted to add:
CGRect borderRect = CGRectInset(rect, 2, 2);
Or, better:
CGFloat lineWidth = 2;
CGRect borderRect = CGRectInset(rect, lineWidth * 0.5, lineWidth * 0.5);
Those circles are just getting clipped to the bounds of the views that draw them. The views must be slightly larger than the circles to be drawn. You can imagine the CGContextStrokeEllipseInRect call tracing a circle of radius 30 and then painting one pixel on each side of the traced curve. Well on the far edges you're going to have one of those pixels just outside the boundary of the view.
Try making your views something like 62x62, or make the circle radius slightly smaller to leave room for the thick stroke in your 60x60 views.
I Wrote this so that you can draw many circles easily.
Add the following code to your .m file:
- (void) circleFilledWithOutline:(UIView*)circleView fillColor:(UIColor*)fillColor outlineColor:(UIColor*)outlinecolor{
CAShapeLayer *circleLayer = [CAShapeLayer layer];
float width = circleView.frame.size.width;
float height = circleView.frame.size.height;
[circleLayer setBounds:CGRectMake(2.0f, 2.0f, width-2.0f, height-2.0f)];
[circleLayer setPosition:CGPointMake(width/2, height/2)];
UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(2.0f, 2.0f, width-2.0f, height-2.0f)];
[circleLayer setPath:[path CGPath]];
[circleLayer setFillColor:fillColor.CGColor];
[circleLayer setStrokeColor:outlinecolor.CGColor];
[circleLayer setLineWidth:2.0f];
[[circleView layer] addSublayer:circleLayer];
}
Then Add the following code to your view did load and replace "yourView" with any view that you want to place the circle in. If you want to make a bunch of circles just add some small views to the page and repeat the code below. The circle will become the size of the view you make.
[self circleFilledWithOutline:self.yourView fillColor:[UIColor redColor] outlineColor:[UIColor purpleColor]];
There is a simple way to draw a fill circle
CGContextFillEllipseInRect(context, CGRectMake(x,y,width,height))
Trianna Brannon's answer on Swift 3
func circleFilledWithOutline(circleView: UIView, fillColor: UIColor, outlineColor:UIColor) {
let circleLayer = CAShapeLayer()
let width = Double(circleView.bounds.size.width);
let height = Double(circleView.bounds.size.height);
circleLayer.bounds = CGRect(x: 2.0, y: 2.0, width: width-2.0, height: height-2.0)
circleLayer.position = CGPoint(x: width/2, y: height/2);
let rect = CGRect(x: 2.0, y: 2.0, width: width-2.0, height: height-2.0)
let path = UIBezierPath.init(ovalIn: rect)
circleLayer.path = path.cgPath
circleLayer.fillColor = fillColor.cgColor
circleLayer.strokeColor = outlineColor.cgColor
circleLayer.lineWidth = 2.0
circleView.layer.addSublayer(circleLayer)
}
And invoke func via:
self.circleFilledWithOutline(circleView: myCircleView, fillColor: UIColor.red, outlineColor: UIColor.blue)

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