I am trying to draw 100+ UIBezierPath circles in the drawRect function of an UIView. I am creating and adding the UIBezierPaths to an array:
for (int i = 0; i < 100; i++) {
UIBezierPath *path = [UIBezierPath bezierPath];
[path addArcWithCenter:centerPoint radius:10 startAngle:0 endAngle:360 clockwise:YES];
path.lineWidth = 2;
[bezierPaths addObject:path];
}
before calling the setNeedDisplay. Then in drawRect I iterate through the array
- (void)drawRect:(CGRect)rect {
for (UIBezierPath *path in bezierPaths) {
[path fill];
[path stroke];
}
}
This looks like an overkill, is there a better way of doing this? Using Profile I can see that the 86% of the MainThread is being used on calling the [path stroke] in drawRect.
I am passing 0 and 360 degrees as opposed to radiants and it is drawing them too many times.
Related
I've trying to do graph like this
But only thing I've got is
I was trying bezier path, but it wont work like i need, just have no idea what to do
that code for bezier
UIBezierPath *path = [UIBezierPath bezierPath];
[path setLineWidth:3.0];
[path setLineCapStyle:kCGLineCapRound];
[path setLineJoinStyle:kCGLineJoinRound];
NSValue *value = [newPoints objectAtIndex:0];
CGPoint p1 = [value CGPointValue];
[path moveToPoint:CGPointMake(0, viewHeight/2)];
for (int k=1; k<[newPoints count];k++) {
NSValue *value = [newPoints objectAtIndex:k];
CGPoint p2 = [value CGPointValue];
CGPoint centerPoint = CGPointMake((p1.x+p2.x)/2, (p1.y+p2.y)/2);
if (p1.y<p2.y) {
centerPoint = CGPointMake(centerPoint.x, centerPoint.y+(fabs(p2.y-centerPoint.y)));
}else if(p1.y>p2.y){
centerPoint = CGPointMake(centerPoint.x, centerPoint.y-(fabs(p2.y-centerPoint.y)));
}
[path addQuadCurveToPoint:p2 controlPoint:centerPoint];
p1 = p2;
}
[path stroke];
it was close to result, but not the same
and I used only top points of graph, not all
current result with this code
or how could I using mask take only things that between bezier line and green line at center? Then i make bars height just all of view and line will cut them like I need
you can try by UIBezierPath , just first use
[urPath moveToPoint:firstPOint];
loop
[urPath addLineToPoint:topOfEveryBar];
I've been using the following code to create views with rounded corners:
- (void)setMaskTo:(UIView*)view byRoundingCorners:(UIRectCorner)corners
{
UIBezierPath* rounded = [UIBezierPath bezierPathWithRoundedRect:view.bounds
byRoundingCorners:corners
cornerRadii:CGSizeMake(_cornerRadius, _cornerRadius)];
CAShapeLayer* shape = [[CAShapeLayer alloc] init];
[shape setPath:rounded.CGPath];
view.layer.mask = shape;
}
So I've been calling it with something like:
[self setMaskTo:someView byRoundingCorners:UIRectCornerAllCorners];
Or:
[self setMaskTo:someOtherView byRoundingCorners:UIRectCornerBottomRight];
I now want to create some views corresponding to one of the bits on the outside of a single rounded corner...
A good approach (think I) is to create the appropriate path so that I can create a CAShapeLayer and then use that as the mask for the view (similar to above).
I'm not sure what the best approach is, which methods etc., when using the UIBezierPath class (or CGPath).
Any suggestions?
Using UIViews and a mask layer, to get the shapes that I wanted, I just needed to play around creating paths with an arc...
I'm aware that the methods for working with a CGPath can be used, but I've worked with what is available with UIBezierPath.
I've used the following methods to create the paths that I need:
- (UIBezierPath*)oppositeArcOfPathForBottomRightCorner
{
float width = _cornerRadius;
UIBezierPath* path = [UIBezierPath new];
[path moveToPoint:CGPointMake(0, width)];
[path addLineToPoint:CGPointMake(width, width)];
[path addLineToPoint:CGPointMake(width, 0)];
[path addArcWithCenter:CGPointMake(0, 0) radius:width startAngle:0 endAngle:(M_PI / 2.0f) clockwise:YES];
[path closePath];
return path;
}
- (UIBezierPath*)oppositeArcOfPathForTopLeftCorner
{
float width = _cornerRadius;
UIBezierPath* path = [UIBezierPath new];
[path moveToPoint:CGPointMake(0, width)];
[path addArcWithCenter:CGPointMake(width, width) radius:width startAngle:M_PI endAngle:(M_PI * 1.5f) clockwise:YES];
[path addLineToPoint:CGPointMake(0, 0)];
[path closePath];
return path;
}
And then something like this to use as a mask with a UIView:
- (void)setMaskTo:(UIView*)view fromPath:(UIBezierPath*)path
{
CAShapeLayer* shape = [[CAShapeLayer alloc] init];
[shape setPath:path.CGPath];
view.layer.mask = shape;
}
Hiiii
I'm trying to draw in a view an arc with dynamic width.
I have built an ARRAY with several UIBezierPath with arcs then I draw one after the other. this is the code:
- (void)drawRect:(CGRect)rect
{
// Drawing code
CGFloat radious;
if (self.bounds.size.width < self.bounds.size.height) {
radious = self.bounds.size.width / 2;
}
else{
radious = self.bounds.size.height / 2;
}
float oneGradeInRadians = (float)(2 * M_PI) / 360.f;
float radiandToDraw = (float)self.finalAngleRadians - self.initialAngleRadians;
float splitMeasure = oneGradeInRadians;
int numberOfArcs = radiandToDraw / splitMeasure;
NSMutableArray *arrayOfBeziersPaths = [NSMutableArray arrayWithCapacity:numberOfArcs];
for (int i = 0; i < numberOfArcs; i++) {
float startAngle = self.initialAngleRadians + (i * splitMeasure);
float endAngle = self.initialAngleRadians +((i + 1) * splitMeasure);
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithArcCenter:CGPointMake(self.frame.size.width/2, self.frame.size.height/2) radius:radious - self.widthLine.floatValue startAngle:startAngle endAngle:endAngle clockwise:YES];
bezierPath.lineWidth = self.widthLine.floatValue + i/20;
float hue = (float)(i / 3.f);
UIColor *color = [UIColor colorWithHue:hue/360.0 saturation:1 brightness:1 alpha:1];
[color setStroke];
[arrayOfBeziersPaths addObject:bezierPath];
}
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetLineJoin(context, kCGLineJoinMiter);
//We saved the context
CGContextSaveGState(context);
for (int i = 0; i < numberOfArcs; i++) {
[(UIBezierPath *)[arrayOfBeziersPaths objectAtIndex:i] stroke];
[(UIBezierPath *)[arrayOfBeziersPaths objectAtIndex:i] fill];
}
//Restore the contex
CGContextRestoreGState(context);
}
In this way I get the right shape but, also I get an annoying lines between the arcs:
I think the problem may be a property to change between arcs, but I'm totally lost, or maybe there is another better way to build this.
I tried creating several UIBezierPath paths and I added them to an unique UIBezierPath, then I stoke and fill that path. I didn't get the annoying lines, but the problem is I can not modify the line width, so I can not get the same effect.
Any idea? thanks
dont create many such paths. just create two arcs (inner circle, exterior circle) and two straight lines joining the ends. then fill the path.
Add below code in a viewDidLoad of an empty viewController and check.
- (void)viewDidLoad
{
[super viewDidLoad];
CGFloat k =0.5522847498;
UIBezierPath *path =[UIBezierPath bezierPath];
CGFloat radius=130;
[path addArcWithCenter:CGPointMake(0, 0) radius:radius startAngle:M_PI_2 endAngle:M_PI clockwise:NO];
CGFloat start=10;
CGFloat increment=5;
[path addLineToPoint:CGPointMake(-radius-start, 0)];
[path addCurveToPoint:CGPointMake(0, -radius-start-increment) controlPoint1:CGPointMake(-radius-start, -(radius +start)*k) controlPoint2:CGPointMake(-(radius+start)*k, -radius-start-increment)];
[path addCurveToPoint:CGPointMake(radius+start+2*increment, 0) controlPoint1:CGPointMake((radius+start+increment)*k, -radius-start-increment) controlPoint2:CGPointMake(radius+start+2*increment, (-radius-start-increment)*k)];
[path addCurveToPoint:CGPointMake(0, radius+start+3*increment) controlPoint1:CGPointMake(radius+start+2*increment, (radius+start+2*increment)*k) controlPoint2:CGPointMake((radius+start+2*increment)*k,radius+start+3*increment)];
[path addLineToPoint:CGPointMake(0,radius)];
CAShapeLayer *layer =[CAShapeLayer layer];
[layer setFrame:CGRectMake(150, 200, 300, 300)];
[layer setFillColor:[UIColor greenColor].CGColor];
[layer setStrokeColor:[UIColor blackColor].CGColor];
[layer setPath:path.CGPath];
[self.view.layer addSublayer:layer];
}
It produced result like,
The problem here is that you’ve chosen a crazy approach to drawing the shape you’re after. If you take a look at it, you’ll see that it’s actually a filled region bounded on the outside by a circular arc, and on the inner edge by a spiral.
What you should do, therefore, is create a single NSBezierPath, add the outer circular arc to it, then add a line to the start of your spiral, append a spiral (you’ll want to approximate it with Bézier segments) and finally call -closePath. After that, you can -fill the path and you’ll get a good looking result rather than the mess you have above.
I have this code where I draw circles on the screen, and I want to remove just the last circle drawn. What can I do? The code is set to draw a circle when I tap twice. I want to remove the last circle drawn when I tap one time.
- (UIBezierPath *)makeCircleAtLocation:(CGPoint)location radius:(CGFloat)radius {
iOSCircle *circle = [[iOSCircle alloc] init];
circle.circleCenter = location;
circle.circleRadius = radius;
[totalCircles addObject:circle];
UIBezierPath *path = [UIBezierPath bezierPath];
[path addArcWithCenter:circle.circleCenter
radius:circle.circleRadius
startAngle:0.0
endAngle:M_PI * 2.0
clockwise:YES];
return path;
}
- (IBAction) tapEvent: (UIGestureRecognizer *) sender
{
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = [[self makeCircleAtLocation:location radius:2.5] CGPath];
shapeLayer.strokeColor = [[UIColor redColor] CGColor];
//shapeLayer.fillColor = nil;
shapeLayer.lineWidth = 2.5;
// Add CAShapeLayer to our view
[self.view.layer addSublayer:shapeLayer];
// Save this shape layer in a class property for future reference,
// namely so we can remove it later if we tap elsewhere on the screen.
self.circleLayer = shapeLayer;
}
}
Create your circle in a distinct CAShapeLayer layer using a CGPath, and add it as a sublayer of your view.layer. That way, you will have total control over that circle (showing or hiding it).
I have a custom UIView and in the drawRect method I create a UIBezierPath and render it to the screen. The issue I am having is that the previous UIBezierPath is removed from the view on the next drawRect call?
How can I keep all of these UIBezierPaths on screen?
- (void)drawRect:(CGRect)rect
{
UIBezierPath *path = [UIBezierPath bezierPath];
// Move to centre and draw an arc.
[path moveToPoint:self.center];
[path addArcWithCenter:self.center
radius:self.radius
startAngle:self.startAngle
endAngle:self.endAngle
clockwise:YES];
[path closePath];
path.usesEvenOddFillRule = YES;
[self.colorToRender setFill];
[path fill];
}
Whenever drawRect: is called you have to draw the whole screen of content. You can't append new drawings to what's already there. So you need to store the whole list of beziers and draw all of them.
Store them in an array and then loop over the array and draw each.
Also, you shouldn't really be creating new beziers in drawRect:...
I am not sure if this is the most convenient way but you have to keep reference to old paths and then combine the new one with them.
CAShapeLayer * shapeLayer; //Sublayer of your view
CGMutablePathRef combinedPath = CGPathCreateMutableCopy(shapeLayer.path);
CGMutablePathRef linePath = CGPathCreateMutable();
//Create your own custom linePath here
//No paths drawn before
if(combinedPath == NULL)
{
combinedPath = linePath;
}
else
{
CGPathAddPath(combinedPath, NULL, linePath);
}
shapeLayer.path = combinedPath;
CGPathRelease(linePath);