I'm trying to build a design app and one of the features allows people to preview different types of custom borders on various assets.
I am trying to get these borders to draw using UIBezierPath.
Right now the system will support:
various border radii (anything from 0 -> a ceiling calculated elsewhere )
different colors for the entire border
I'm trying to implement:
The ability to have different border line widths.
I think I should be able to achieve this by drawing each path with a different width, but I can't figure out how to change it from path to path. Can anyone help me out?
Thanks.
Click top see the border script in action
+(void)setBorderOnView :(id)object withWidth:(float)width andBorders:(NSArray*)borders ofColor:(UIColor*)color andRadius:(float)radius andRadii:(NSArray*)radii
{
UIView *objectView = object;
// Add half a pixel to compensate for border stroke, which puts .5 pixels outside of view.
float borderWidth = width+0.5;
float topLeftRadius = radius;
float topRightRadius = radius;
float bottomRightRadius = radius;
float bottomLeftRadius = radius;
CAShapeLayer *roundedCornerLayer = [CAShapeLayer layer];
CAShapeLayer *cornerMaskLayer = [CAShapeLayer layer];
UIColor * borderColor = color;
roundedCornerLayer.strokeColor = borderColor.CGColor;
// Begin corner path
UIBezierPath *roundedCorners = [UIBezierPath bezierPath];
[roundedCorners moveToPoint:CGPointMake(0, objectView.frame.size.height - bottomLeftRadius)];
[roundedCorners addLineToPoint:CGPointMake(0, 0 + topLeftRadius)];
if(topLeftRadius != 0){
[roundedCorners addArcWithCenter:CGPointMake(topLeftRadius, topLeftRadius)
radius:radius
startAngle:DEGREES_TO_RADIANS(180)
endAngle:DEGREES_TO_RADIANS(270)
clockwise:YES];
}
[roundedCorners addLineToPoint:CGPointMake(objectView.frame.size.width - topRightRadius, 0)];
if(topRightRadius != 0){
[roundedCorners addArcWithCenter:CGPointMake(objectView.frame.size.width - topRightRadius, 0 + topRightRadius)
radius:radius
startAngle:DEGREES_TO_RADIANS(270)
endAngle:DEGREES_TO_RADIANS(360)
clockwise:YES];
}
[roundedCorners addLineToPoint:CGPointMake(objectView.frame.size.width, objectView.frame.size.height - bottomRightRadius)];
if(bottomRightRadius != 0){
[roundedCorners addArcWithCenter:CGPointMake(objectView.frame.size.width - bottomRightRadius, objectView.frame.size.height - bottomRightRadius)
radius:radius
startAngle:DEGREES_TO_RADIANS(0)
endAngle:DEGREES_TO_RADIANS(90)
clockwise:YES];
}
[roundedCorners addLineToPoint:CGPointMake(0 + bottomLeftRadius, objectView.frame.size.height)];
if(bottomLeftRadius != 0){
[roundedCorners addArcWithCenter:CGPointMake(0 + bottomLeftRadius, objectView.frame.size.height - bottomLeftRadius)
radius:radius
startAngle:DEGREES_TO_RADIANS(90)
endAngle:DEGREES_TO_RADIANS(180)
clockwise:YES];
}
roundedCornerLayer.path = roundedCorners.CGPath;
roundedCornerLayer.fillColor = [UIColor clearColor].CGColor;
roundedCornerLayer.lineWidth = borderWidth;
[cornerMaskLayer setPath:roundedCorners.CGPath];
objectView.layer.mask = cornerMaskLayer;
[objectView.layer addSublayer:roundedCornerLayer];
}
roundedCornerLayer.lineWidth = borderWidth; sets the width of the path. I'm not sure if you want the same path to have different widths or different paths with different widths.
If you want different widths in one path then you must break it into separate subpaths and each will have a different lineWidth.
If you want to have different paths then just change the value of width when you call the method.
Did I miss anything?
Related
how can I create a label below like this image in iOS (Objective-c)?
My problem is not all corners, because I can round top-left & bottom-right corners by writing codes like this:
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.path = [UIBezierPath bezierPathWithRoundedRect:self.customView.bounds
byRoundingCorners:UIRectCornerBottomRight | UIRectCornerTopLeft
cornerRadii:(CGSize){7.0, 7.0}].CGPath;
self.customLabel.layer.mask = maskLayer;
and the result is this:
Now how can I write codes for top-right & bottom-left to reach the goal?
Update:
I didn't use the height as a variable in my equations it may affect the results.
Better results
- (void)drawRect:(CGRect)rect
{
CGFloat curveWidth = 40;
CGFloat width = self.frame.size.width;
CGFloat height = self.frame.size.height;
// UIEdgeInsets insets = {0, curveWidth, 0, curveWidth};
// [super drawTextInRect:UIEdgeInsetsInsetRect(rect, insets)];
UIBezierPath *path = [UIBezierPath bezierPath];
UIColor *fillColor = [UIColor orangeColor];
[fillColor setFill];
[path moveToPoint:CGPointMake(0, height)];
[path addCurveToPoint:CGPointMake(curveWidth*2, 0) controlPoint1:CGPointMake(curveWidth*0.75, height) controlPoint2:CGPointMake(curveWidth*0.25, 0)];
[path addLineToPoint:CGPointMake(width, 0)];
[path addCurveToPoint:CGPointMake(width-curveWidth*2, height) controlPoint1:CGPointMake(width-curveWidth*0.75, 0) controlPoint2:CGPointMake(width-curveWidth*0.25, height)];
[path closePath];
[path fill];
[super drawRect: rect];
}
Original Answer:
I wrote the code for you. follow the steps:
Create a custom UILabel by subclassing it. (code below)
Add a UILabel to your view using interface builder.
Add your constraints.
In the Identifier inspector tab, change class to your.
--
#implementation CurvedLabel
- (id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder])
{
}
return self;
}
- (void)drawRect:(CGRect)rect
{
CGFloat curveWidth = 30;
CGFloat width = self.frame.size.width;
CGFloat height = self.frame.size.height;
// UIEdgeInsets insets = {0, curveWidth, 0, curveWidth};
// [super drawTextInRect:UIEdgeInsetsInsetRect(rect, insets)];
UIBezierPath *path = [UIBezierPath bezierPath];
UIColor *fillColor = [UIColor orangeColor];
[fillColor setFill];
[path moveToPoint:CGPointMake(0, height)];
[path addCurveToPoint:CGPointMake(curveWidth*2, 0) controlPoint1:CGPointMake(curveWidth, height) controlPoint2:CGPointMake(0, 0)];
[path addLineToPoint:CGPointMake(width, 0)];
[path addCurveToPoint:CGPointMake(width-curveWidth*2, height) controlPoint1:CGPointMake(width-curveWidth, 0) controlPoint2:CGPointMake(width, height)];
[path closePath];
[path fill];
[super drawRect: rect];
}
#end
I don't believe there is any simple way of doing that, you could use a png file, if that is not a option you should look in to UIBezierPath, check this link out: ios drawing concepts
Sure you can do it with a UIBezierPath.
I have made a pretty simple setup you can use.
// This is the parallelogram's corner structure
// 1------2
// / /
// / /
// 4------3
int xOffset = 150; // x Offset on the parent view - here self.view
int yOffset = 50; // y Offset on the parent view - here self.view
int flatten = 50; // The difference from center of curve to spike point (try to adjust it and see what it does)
int width = 200;
int height = 100;
UIBezierPath * maskLayer = [UIBezierPath bezierPath];
// Start at corner 1
[maskLayer moveToPoint: CGPointMake(xOffset+flatten, yOffset)];
// Go to corner 2
[maskLayer addLineToPoint: CGPointMake(xOffset+width+flatten, yOffset)];
// Curve it down to corner 3
[maskLayer addCurveToPoint: CGPointMake(xOffset+width-flatten, height+yOffset) controlPoint1: CGPointMake(xOffset+width, yOffset) controlPoint2:CGPointMake(xOffset+width, height+yOffset)];
// Go straight to corner 4
[maskLayer addLineToPoint: CGPointMake(xOffset, height+yOffset)];
// Curve it back to corner 1
[maskLayer addCurveToPoint: CGPointMake(xOffset+(2*flatten), yOffset) controlPoint1:CGPointMake(xOffset+flatten, yOffset+height) controlPoint2: CGPointMake(xOffset+flatten, yOffset)];
// Create the shape and fill it with a color
CAShapeLayer *maskShapeLayer = [CAShapeLayer layer];
maskShapeLayer.path = maskLayer.CGPath;
maskShapeLayer.fillColor = [UIColor redColor].CGColor;
// add the layer to a view's layer (here self.view.layer)
[self.view.layer addSublayer:maskShapeLayer];
If you wanna make the two ´spikes´ more rounded, you can make the y-value in the addCurveToPoint points closer to the parallelogram's y-center-axis (height/2).
You will have to useaddCurveToPoint of UIBezierPath to make it curvy by giving it controlPoint1 and controlPoint2
For reference, you can look at Apple's Documentation
Also, this is a helpful reference too.
You will have to play around with UIBezierPath's properties
I'm trying to create an arc, like a half circle, that i can animate with different colors. I thought I would start with creating a bezier path for the arc and setting the line width to something large. This is what I have so far:
CAShapeLayer *layer = (CAShapeLayer *)self.layer;
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
CGPoint startPoint = CGPointMake(self.bounds.origin.x, self.bounds.origin.y + self.bounds.size.height / 2);
[bezierPath moveToPoint:startPoint];
layer.fillColor = [UIColor clearColor].CGColor;
layer.strokeColor = [UIColor redColor].CGColor;
CGFloat strokeSize = 10;
CGPoint endPoint = CGPointMake(self.bounds.origin.x + self.bounds.size.width, self.bounds.origin.y + self.bounds.size.height / 2);
[bezierPath setLineWidth:100];
[bezierPath addQuadCurveToPoint:endPoint controlPoint:CGPointMake(self.bounds.origin.x + self.bounds.size.width / 2, self.bounds.origin.y)];
bezierPath.lineCapStyle = kCGLineCapRound;
layer.path = bezierPath.CGPath;
When I see my arc though, it doesn't look any bigger than a thin path. Is this the way to create something like that? Or do I need to create two paths and somehow fill the area between them?
You need to set the layer's lineWidth, not the bezier path's,
layer.lineWidth = 100;
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 want to write a UILabel that can print square root expressions with a roof on it instead of just a simple √x. There should be a line over the x there as if it's written on paper.
Using Quartz 2D (the QuartzCore framework), you could just draw the line over it:
Thus, given
self.label.text = #"√23+45";
[self addSquareRootTopTo:self.label];
This would draw a line over the characters after the √ symbol:
- (void)addSquareRootTopTo:(UILabel *)label
{
NSRange range = [label.text rangeOfString:#"√"];
if (range.location == NSNotFound)
return;
NSString *stringThruSqrtSymbol = [label.text substringToIndex:range.location + 1];
NSString *stringAfterSqrtSymbol = [label.text substringFromIndex:range.location + 1];
CGSize sizeThruSqrtSymbol;
CGSize sizeAfterSqrtSymbol;
// get the size of the string given the label's font
if ([stringThruSqrtSymbol respondsToSelector:#selector(sizeWithAttributes:)])
{
// for iOS 7
NSDictionary *attributes = #{NSFontAttributeName : label.font};
sizeThruSqrtSymbol = [stringThruSqrtSymbol sizeWithAttributes:attributes];
sizeAfterSqrtSymbol = [stringAfterSqrtSymbol sizeWithAttributes:attributes];
}
else
{
// for earlier versions of iOS
sizeThruSqrtSymbol = [stringThruSqrtSymbol sizeWithFont:label.font];
sizeAfterSqrtSymbol = [stringAfterSqrtSymbol sizeWithFont:label.font];
}
// create path for line over the stuff after the square root sign
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(label.frame.origin.x + sizeThruSqrtSymbol.width, label.frame.origin.y)];
[path addLineToPoint:CGPointMake(label.frame.origin.x + sizeThruSqrtSymbol.width + sizeAfterSqrtSymbol.width, label.frame.origin.y)];
// now add that line to a CAShapeLayer
CAShapeLayer *layer = [CAShapeLayer layer];
layer.path = path.CGPath;
layer.lineWidth = 1.0;
layer.strokeColor = [[UIColor blackColor] CGColor];
layer.fillColor = [[UIColor clearColor] CGColor];
[self.view.layer addSublayer:layer];
}
That yields a less than satisfactory gap:
You could play around with this to nudge the top of the square root symbol down, but the alternative approach would be to use Quartz 2D to draw the entire square root symbol. Thus, take the square root symbol out of the label's text value:
self.label.text = #"23+45";
[self addSquareRootTo:self.label];
And then draw the whole square root symbol in Quartz 2D:
static CGFloat const part1 = 0.12; // tiny diagonal will be 12% of the height of the text frame
static CGFloat const part2 = 0.35; // medium sized diagonal will be 35% of the height of the text frame
- (void)addSquareRootTo:(UILabel *)label
{
CGSize size;
if ([label.text respondsToSelector:#selector(sizeWithAttributes:)])
{
NSDictionary *attributes = #{NSFontAttributeName : label.font};
size = [label.text sizeWithAttributes:attributes];
}
else
{
size = [label.text sizeWithFont:label.font];
}
UIBezierPath *path = [UIBezierPath bezierPath];
// it's going to seem strange, but it's probably easier to draw the square root size
// right to left, so let's start at the top right of the text frame
[path moveToPoint:CGPointMake(label.frame.origin.x + size.width, label.frame.origin.y)];
// move to the top left
CGPoint point = CGPointMake(label.frame.origin.x, label.frame.origin.y);
[path addLineToPoint:point];
// now draw the big diagonal line down to the bottom of the text frame (at 15 degrees)
point.y += size.height;
point.x -= sinf(15 * M_PI / 180) * size.height;
[path addLineToPoint:point];
// now draw the medium sized diagonal back up (at 30 degrees)
point.y -= size.height * part2;
point.x -= sinf(30 * M_PI / 180) * size.height * part2;
[path addLineToPoint:point];
// now draw the tiny diagonal back down (again, at 30 degrees)
point.y += size.height * part1;
point.x -= sinf(30 * M_PI / 180) * size.height * part1;
[path addLineToPoint:point];
// now add the whole path to our view
CAShapeLayer *layer = [CAShapeLayer layer];
layer.path = path.CGPath;
layer.lineWidth = 1.0;
layer.strokeColor = [[UIColor blackColor] CGColor];
layer.fillColor = [[UIColor clearColor] CGColor];
[self.view.layer addSublayer:layer];
}
That looks a little better:
Clearly, you can implement this any way you want (using either of the above, or probably better, subclassing a UIView to putting this, or its CoreGraphics equivalent, in drawRect), but it illustrates the idea of drawing the square root symbol yourself.
By the way, if using Xcode version prior 5, you'll have to manually add the QuartzCore.framework to your project and then include the appropriate header:
#import <QuartzCore/QuartzCore.h>
I have made Circle With below code
//Color Declaration
UIColor *color = [UIColor colorWithRed:0 green:0.429 blue:0 alpha:1];
//Drawing Circle
CGRect circleRect = CGRectMake(20, 20, 170, 170);
UIBezierPath *circlePath = [UIBezierPath bezierPath];
[circlePath addArcWithCenter:CGPointMake(CGRectGetMidX(circleRect), CGRectGetMidY(circleRect))
radius:CGRectGetWidth(circleRect)/2 startAngle:0 * M_PI/180
endAngle:289 * M_PI/180
clockwise:YES];
[circlePath addLineToPoint:CGPointMake(CGRectGetMidX(circleRect), CGRectGetMidY(circleRect))];
[circlePath closePath];
[color setFill];
[circlePath fill];
I have Validated Touches Code with Area of Circle With the Below code,
- (BOOL)validatePoint:(CGPoint)myPoint
{
// calculate how far from centre we are with Pythagorean
// √ a2 + b2
CGFloat a = abs(myPoint.x - (self.view.bounds.size.width/2));
CGFloat b = abs(myPoint.y - (self.view.bounds.size.height/2));
CGFloat distanceFromCentre = sqrt(pow(a,2) + pow(b,2));
if((distanceFromCentre > self.minRadiusSize) && (distanceFromCentre < self.maxRadiusSize)){
return YES;
}else{
// not inside doughnut
return NO;
}
}
I have Validated the Circle with Above code.When above code is true then we can add touches to Circle.
Similarly I have to validate the sector (Portion) of the Circle.
My Requirement is, I have to validate the Sector of Circle. were I have to Detect the sector(Portion) of the drawn Circle.
I have Formula
[Area of Sector = ½ × (θ × π/180) × r2 (when θ is in degrees)].
If all you are trying to do is to check if the point is inside the circle sector then you should use the same path to hit test the point
BOOL isInsideSector = [circlePath containsPoint:myPoint];