I am trying to add a top and bottom border to a uibutton my function to add the border looks like this
CALayer *topBorder = [CALayer layer];
topBorder.frame = CGRectMake(os, 1.0f, b.bounds.size.width - (os * 2.0f), 1.0f);
topBorder.backgroundColor = [c1 CGColor];
[b.layer addSublayer:topBorder];
CALayer *bottomBorder = [CALayer layer];
bottomBorder.frame = CGRectMake(os, b.bounds.size.height, b.bounds.size.width - (os * 2.0f), 1.0f);
bottomBorder.backgroundColor = [c1 CGColor];
[b.layer addSublayer:bottomBorder];
//os..offset, b..uibutton, c1..color
this works fine when i call the function in viewDidAppear (but there is a delay then) but when i put it in viewdidlayoutsubviews it adds an additional line, what somehow looks like this
i set it up with a leading and trailing space to its superview, what a i doing wrong here?
The viewDidAppear is executed when you view appears on screen and every time come back from other view controller in your navigation flow. So your code add lines several times. You can review the view controller lifecycle here.
If you want to remove the "delay" and executed once, write your code in the viewDidLoad.
UPDATE
I see two approach. Create a UIButton subclass and then:
If you want to keep working with CALayers, you'll need to call a redraw method always your button change its size. If the change is animated will appear weird artifact.
The good one. I recommend you use the 'drawRect' to paint this lines and use the button frame to calculate line XY dynamically. If you button change its size, animated or not… no problem, Core Animation do all the job.
- (void)drawRect:(CGRect)rect
{
// Frames
CGRect frame = self.bounds;
// Parameters (change these values if you want to change appearance)
UIColor *lineColor = [UIColor colorWithRed: 0.5 green: 0.5 blue: 0.5 alpha: 1];
CGFloat topRatioPositionY = 0.02;
CGFloat middleRatioPositionY = 0.6;
CGFloat bottomRatioPositionY = 0.98;
CGFloat middleRatioPositionX = 0.33333;
CGFloat lineWidth = 1.0;
//// Top line
UIBezierPath* bezierPath = [UIBezierPath bezierPath];
[bezierPath moveToPoint: CGPointMake(CGRectGetMinX(frame), CGRectGetMinY(frame) + topRatioPositionY * CGRectGetHeight(frame))];
[bezierPath addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 1.00000 * CGRectGetWidth(frame), CGRectGetMinY(frame) + topRatioPositionY * CGRectGetHeight(frame))];
[lineColor setStroke];
bezierPath.lineWidth = lineWidth;
[bezierPath stroke];
// Middle line
UIBezierPath* bezier2Path = [UIBezierPath bezierPath];
[bezier2Path moveToPoint: CGPointMake(CGRectGetMinX(frame), CGRectGetMinY(frame) + bottomRatioPositionY * CGRectGetHeight(frame))];
[bezier2Path addLineToPoint: CGPointMake(CGRectGetMinX(frame) + 1.00000 * CGRectGetWidth(frame), CGRectGetMinY(frame) + bottomRatioPositionY * CGRectGetHeight(frame))];
[lineColor setStroke];
bezier2Path.lineWidth = lineWidth;
[bezier2Path stroke];
// Bottom Line
UIBezierPath* bezier3Path = [UIBezierPath bezierPath];
[bezier3Path moveToPoint: CGPointMake(CGRectGetMinX(frame), CGRectGetMinY(frame) + middleRatioPositionY * CGRectGetHeight(frame))];
[bezier3Path addLineToPoint: CGPointMake(CGRectGetMinX(frame) + middleRatioPositionX * CGRectGetWidth(frame), CGRectGetMinY(frame) + middleRatioPositionY * CGRectGetHeight(frame))];
[lineColor setStroke];
bezier3Path.lineWidth = lineWidth;
[bezier3Path stroke];
}// drawRect:
I've attached this source code with lines with CALayer and with UIBezierPath solutions. Touch on buttons and see the difference when they change.
Related
Is it possible to draw a path around the visible part of a UIBezierPath?
Here's an example of my problem
Here's what I'd like to accomplish
Here's what I got so far:
- (void)drawRect:(CGRect)rect {
CGFloat side = MIN(rect.size.width, rect.size.height);
CGPoint center = CGPointMake(rect.size.width / 2.0f, rect.size.height / 2.0f);
UIColor *yinYangColor = [UIColor whiteColor];
UIBezierPath *yinYangPath = [UIBezierPath bezierPath];
// Draw Yin&Yang part
[yinYangPath addArcWithCenter:CGPointMake(center.x, center.y - side / 4.0f) radius:side / 4.0f startAngle:M_PI_2 endAngle:-M_PI_2 clockwise:YES];
[yinYangPath addArcWithCenter:CGPointMake(center.x, center.y + side / 4.0f) radius:side / 4.0f startAngle:M_PI_2 endAngle:-M_PI_2 clockwise:NO];
[yinYangPath addArcWithCenter:CGPointMake(center.x, center.y) radius:side / 2.0f startAngle:-M_PI_2 endAngle:M_PI_2 clockwise:YES];
[yinYangPath closePath];
[yinYangColor setFill];
[yinYangPath fill];
// Add border
CAShapeLayer *borderLayer = [[CAShapeLayer alloc] init];
borderLayer.path = yinYangPath.CGPath;
borderLayer.fillColor = [UIColor clearColor].CGColor;
borderLayer.strokeColor = [UIColor blackColor].CGColor;
borderLayer.lineWidth = 5.0f;
[self.layer addSublayer:borderLayer];
}
The angle π/2 radians is along the positive y axis.
In standard UIKit geometry, the positive y axis points down toward the bottom of the screen. Therefore the top arc (at center.y - side/4) needs to start at angle -π/2 and end at angle π/2. Since you got these backward, your second arc doesn't start where your first arc ended, so your path contains a straight line connecting those points. Ditto for your second and third arcs. The single straight line visible in your image is actually the combination of those two lines.
Also, incidentally, the rect passed to drawRect: is in theory not necessarily the bounds of the view. It's better not to treat it as such.
Also also, you shouldn't add sublayers in drawRect:. You should do that in init or layoutSubviews and you should make sure you don't duplicate layers. I guess maybe you're using a CAShapeLayer because you don't want the border cut off. I would solve that by insetting the view bounds by the border width:
- (void)drawRect:(CGRect)dirtyRect {
CGFloat lineWidth = 4;
CGRect rect = CGRectInset(self.bounds, lineWidth / 2, lineWidth / 2);
CGFloat side = MIN(rect.size.width, rect.size.height);
CGPoint center = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
UIBezierPath *path = [UIBezierPath bezierPath];
CGFloat smallRadius = side / 4;
[path addArcWithCenter:CGPointMake(center.x, center.y - smallRadius) radius:smallRadius startAngle:-M_PI_2 endAngle:M_PI_2 clockwise:NO];
[path addArcWithCenter:CGPointMake(center.x, center.y + smallRadius) radius:smallRadius startAngle:-M_PI_2 endAngle:M_PI_2 clockwise:YES];
[path addArcWithCenter:center radius:side / 2 startAngle:M_PI_2 endAngle:-M_PI_2 clockwise:NO];
[path closePath];
[path setLineJoinStyle:kCGLineJoinRound];
[path setLineWidth:lineWidth];
[[UIColor whiteColor] setFill];
[path fill];
[[UIColor blackColor] setStroke];
[path stroke];
}
Result:
If you want the bottom tip to be more pointy, I would do that by clipping all drawing to the path, then drawing the border twice as thick. Half the border will be drawn outside the path and clipped away, leaving a sharp point. In this case, you don't have to inset the bounds.
- (void)drawRect:(CGRect)dirtyRect {
CGFloat lineWidth = 4 * 2;
CGRect rect = self.bounds;
CGFloat side = MIN(rect.size.width, rect.size.height);
CGPoint center = CGPointMake(CGRectGetMidX(rect), CGRectGetMidY(rect));
UIBezierPath *path = [UIBezierPath bezierPath];
CGFloat smallRadius = side / 4;
[path addArcWithCenter:CGPointMake(center.x, center.y - smallRadius) radius:smallRadius startAngle:-M_PI_2 endAngle:M_PI_2 clockwise:NO];
[path addArcWithCenter:CGPointMake(center.x, center.y + smallRadius) radius:smallRadius startAngle:-M_PI_2 endAngle:M_PI_2 clockwise:YES];
[path addArcWithCenter:center radius:side / 2 startAngle:M_PI_2 endAngle:-M_PI_2 clockwise:NO];
[path closePath];
[path setLineJoinStyle:kCGLineJoinRound];
[path addClip];
[path setLineWidth:lineWidth];
[[UIColor whiteColor] setFill];
[path fill];
[[UIColor blackColor] setStroke];
[path stroke];
}
Result:
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];
In one of my application, I am trying to draw a gradient arc with rounded edges. Like the following image.
This is what I have done so far using the following code.
-(void)startArc
{
UIBezierPath *roundedArc = [self arcWithRoundedCornerAt:centerPoint startAngle:DEGREES_TO_RADIANS(-90) endAngle:DEGREES_TO_RADIANS(90) innerRadius:width-20 outerRadius:width cornerRadius:0];
CAShapeLayer *mask = [CAShapeLayer new];
[mask setPath:roundedArc.CGPath];
[mask setFrame:_outerView.bounds];
[mask setShouldRasterize:YES];
[mask setRasterizationScale:[UIScreen mainScreen].scale];
CAGradientLayer *gradient = [CAGradientLayer new];
[gradient setFrame:_outerView.bounds];
// [gradient setColors:[NSArray arrayWithObjects:(id)[[UIColor colorWithRed:0.86 green:0.91 blue:0.96 alpha:1.0f] CGColor],(id)[[UIColor colorWithRed:0.98 green:0.99 blue:0.99 alpha:1.0f] CGColor], nil]];
[gradient setColors:[NSArray arrayWithObjects:(id)[UIColor colorWithRed:0.19 green:0.64 blue:0.89 alpha:1.0].CGColor,(id)[UIColor colorWithRed:0.14 green:0.76 blue:0.56 alpha:1.0f].CGColor, nil]];
[gradient setMask:mask];
[_outerView.layer addSublayer:gradient];
}
- (UIBezierPath *)arcWithRoundedCornerAt:(CGPoint)center
startAngle:(CGFloat)startAngle
endAngle:(CGFloat)endAngle
innerRadius:(CGFloat)innerRadius
outerRadius:(CGFloat)outerRadius
cornerRadius:(CGFloat)cornerRadius
{
CGFloat innerTheta = asin(cornerRadius / 2.0 / (innerRadius + cornerRadius)) * 2.0;
CGFloat outerTheta = asin(cornerRadius / 2.0 / (outerRadius - cornerRadius)) * 2.0;
UIBezierPath *path = [UIBezierPath bezierPath];
[path addArcWithCenter:center
radius:innerRadius + cornerRadius
startAngle:endAngle - innerTheta
endAngle:startAngle + innerTheta
clockwise:false];
[path addArcWithCenter:center
radius:outerRadius - cornerRadius
startAngle:startAngle + outerTheta
endAngle:endAngle - outerTheta
clockwise:true];
[path closePath];
return path;
}
With the above code, I have achieved the following
Can someone let me know on how to achieve that rounded edges ? The one which I have implemented have sharp edges.
The easiest approach is probably:
Create a single arc path (along the middle of your target arc) as a path
Set the line width (20 in your case) and the line cap (rounded caps)
Convert the path into a stroked path (CGContextReplacePathWithStrokedPath)
Continue with your existing code for the gradient
The stroking will convert your 90 degree arc path, which is infinitely narrow, into an outline of a line that is 20 pixel wide and has rounded line endings.
The code could approximately look like this:
- (UIBezierPath *)arcWithRoundedCornerAt:(CGPoint)center
startAngle:(CGFloat)startAngle
endAngle:(CGFloat)endAngle
innerRadius:(CGFloat)innerRadius
outerRadius:(CGFloat)outerRadius
cornerRadius:(CGFloat)cornerRadius
context:(CGContext)context
{
CGFloat radius = (innerRadius + outerRadius) / 2.0;
CGContextAddArc(context, center.x, center.y, radius, startAngle, endAngle, true);
CGContextSetLineCap(context, kCGLineCapRound);
CGContextSetLineWidth(context, outerRadius - innerRadius);
CGContextReplacePathWithStrokedPath(context)
return [UIBezierPath bezierPathWithCGPath: CGContextCopyPath(context)];
}
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;
I'm trying to create a circular progress view that updates with a timer. I am trying to get 4 sections with different colours. I've managed to update the circle with one colour but I'm having trouble figuring out how to split it into 4 quarter circles.
#implementation CircularProgressBar
- (void)drawRect:(CGRect)rect
{
UIBezierPath* bezierPath = [UIBezierPath bezierPath];
// Create our arc, with the correct angles
[bezierPath addArcWithCenter:CGPointMake(160, 180)
radius:120
startAngle:0
endAngle: DEGREES_TO_RADIANS(360)
clockwise:YES];
// Set the display for the path, and stroke it
bezierPath.lineWidth = 12;
[[UIColor whiteColor] setStroke];
[bezierPath stroke];
UIBezierPath* progressPath = [UIBezierPath bezierPath];
// Create our arc, with the correct angles
[progressPath addArcWithCenter:CGPointMake(160, 180)
radius:120
startAngle:-M_PI_2
endAngle:-M_PI_2 + (2/M_PI) * self.progress
clockwise:YES];
// Set the display for the path, and stroke it
progressPath.lineWidth = 12;
[[UIColor redColor] setStroke];
[progressPath stroke];
UIBezierPath* progressPath1 = [UIBezierPath bezierPath];
// Create our arc, with the correct angles
[progressPath1 addArcWithCenter:CGPointMake(160, 180)
radius:120
startAngle:-M_PI_2 + (2/M_PI)
endAngle:M_PI_2* self.progress1/
clockwise:YES];
// Set the display for the path, and stroke it
progressPath1.lineWidth = 12;
[[UIColor grayColor] setStroke];
[progressPath1 stroke];
}
-(void)setProgress:(CGFloat)progress {
//update progress with timer
if (progress != _progress) {
_progress = progress;
if(progress > 1.1)
_progress1=_progress;
[self setNeedsDisplay];
}
}
Timer Class
-(float) progress {
return (float)self.count/450.0;
}
timer is 1800 seconds. Divided by 4 is 450 seconds per section
I made a small library for this purpose, very flexible with tons of options : https://github.com/kirualex/KAProgressLabel
A simple configuration looks like this :
[self.pLabel setTrackWidth: 2.0];
[self.pLabel setProgressWidth: 4];
self.pLabel.fillColor = [[UIColor lightGrayColor] colorWithAlphaComponent:.3];
self.pLabel.trackColor = self.startSlider.tintColor;
self.pLabel.progressColor = [UIColor greenColor];
You can download useful and very easy to embedd types of progressview on the link http://code4app.net/category/progress