I have implemented a basic line drawing animation for a progress bar.The line has a round cap edge.When I moving the slider the line drawing the ending of line is having a rounded cap but in the starting its a square. below is the code I am using am i missing anything??
Image Attached: expected output
Below Code I tried
#interface ViewController ()
{
CGPoint center;
CGFloat radius;
}
#end
#implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
center = CGPointMake(200.0, 200.0);
self.view.layer.backgroundColor = [[UIColor grayColor] CGColor];
}
- (IBAction)greenSlider:(UISlider *)sender {
[self.view.layer addSublayer:[self drawArcAtCenter:center startAngle:sender.minimumValue endAngle:sender.value radius:100.0 withColor:[UIColor greenColor]]];
}
- (IBAction)blueSlider:(UISlider *)sender {
int value = (int)sender.value;
if (value > 360) {
value = value % 90;
}
[self.view.layer addSublayer:[self drawArcAtCenter:center startAngle:sender.minimumValue endAngle:(float)value radius:100 withColor:[UIColor blueColor]]];
}
- (CAShapeLayer *) drawArcAtCenter:(CGPoint)newCenter startAngle:(CGFloat)sAngle endAngle:(CGFloat)bAngle radius:(CGFloat) radious withColor:(UIColor*) color
{
CGFloat begAngle = sAngle * 3.1459 / 180.0;
CGFloat endAngle = bAngle * 3.1459 / 180.0;
CAShapeLayer *layer = [CAShapeLayer layer];
UIBezierPath *path = [UIBezierPath bezierPath];
[path addArcWithCenter:newCenter radius:radious startAngle:begAngle endAngle:endAngle clockwise:YES];
layer.path = [path CGPath];
layer.strokeColor = [color CGColor];
layer.fillColor = [[UIColor clearColor] CGColor];;
layer.lineWidth = 50.0;
layer.strokeEnd = 50.0f;
layer.lineCap = kCALineCapRound;
layer.cornerRadius = 30.0f;
return layer;
}
Related
So I have a pie chart/color wheel that I made using CAShapeLayer and UIBezierPath. The wheel can have a max of 8 sections. I need it so each section is a different color. I've tried setting up an array (colorsArray) with different colors and inserting into the CAShapeLayer's fillColor method but it just takes the last color in the colorsArray. Is there a way to make sure a different color is in each layer section that is created? I'm a little lost right now. Here is my code below. Thanks in advance.
-(void) drawWheel {
CGPoint circleCenter = CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2);
//1 Create a view that we’ll put everything else inside.
container = [[UIView alloc] initWithFrame:self.frame];
container.backgroundColor = [UIColor whiteColor];
container.layer.cornerRadius = 100.0;
container.layer.borderColor = [UIColor blackColor].CGColor;
container.layer.borderWidth = 1.0;
CGFloat angleSize = 2*M_PI/numberOfSections;
for (int i = 0; i < numberOfSections; i++) {
CAShapeLayer *slice = [CAShapeLayer layer];
*************************************************
UIColor * color = [UIColor blueColor];
//THIS MAKES THE WHOLE THING One Color - the last color in the colorsArray
for (int i = 0; i < self.colorsArray.count; i++) {
color = [self.colorsArray objectAtIndex:i];
slice.fillColor = (__bridge CGColorRef)(color);
}
*************************************************
slice.strokeColor = [UIColor whiteColor].CGColor;
slice.lineWidth = 3.0;
CGPoint center = CGPointMake(100.0, 100.0);
CGFloat radius = 100.0;
CGFloat startAngle = angleSize * i;
UIBezierPath *piePath = [UIBezierPath bezierPath];
[piePath moveToPoint:center];
[piePath addLineToPoint:circleCenter];
[piePath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:startAngle + angleSize clockwise:YES];
[piePath closePath]; // this will automatically add a straight line to the center
slice.path = piePath.CGPath;
int i = 0;
i++;
[container.layer addSublayer:slice];
}
//7 Adds the container to the main control.
container.userInteractionEnabled = NO;
[self addSubview:container];
//8 Initialize sectors
sectors = [NSMutableArray arrayWithCapacity:numberOfSections];
if (numberOfSections % 2 == 0) {
[self buildSectorsEven];
} else {
[self buildSectorsOdd];
}
}
I figured it out. I took loop that goes through my colorArray. I assigned a variable of "UIColor *color" to the object at the index (i) that matches the number of sections in my for loop "color = [self.colorsArray objectAtIndex:i];" I then assigned the variable color to the slice's fillColor.
Snippet:
for (int i = 0; i < numberOfSections; i++) {
CAShapeLayer *slice = [CAShapeLayer layer];
UIColor *color;
color = [self.colorsArray objectAtIndex:i];
slice.fillColor = [UIColor colorWithCGColor:(__bridge CGColorRef _Nonnull)(color)].CGColor;
Full Code:
-(void) drawWheel {
CGPoint circleCenter = CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2);
//1 Create a view that we’ll put everything else inside.
container = [[UIView alloc] initWithFrame:self.frame];
container.backgroundColor = [UIColor whiteColor];
container.layer.cornerRadius = 100.0;
container.layer.borderColor = [UIColor blackColor].CGColor;
container.layer.borderWidth = 1.0;
CGFloat angleSize = 2*M_PI/numberOfSections;
for (int i = 0; i < numberOfSections; i++) {
CAShapeLayer *slice = [CAShapeLayer layer];
UIColor *color;
color = [self.colorsArray objectAtIndex:i];
slice.strokeColor = [UIColor whiteColor].CGColor;
slice.lineWidth = 3.0;
slice.fillColor = [UIColor colorWithCGColor:(__bridge CGColorRef _Nonnull)(color)].CGColor;
CGPoint center = CGPointMake(100.0, 100.0);
CGFloat radius = 100.0;
CGFloat startAngle = angleSize * i;
UIBezierPath *piePath = [UIBezierPath bezierPath];
[piePath moveToPoint:center];
[piePath addLineToPoint:circleCenter];
[piePath addArcWithCenter:center radius:radius startAngle:startAngle endAngle:startAngle + angleSize clockwise:YES];
[piePath closePath]; // this will automatically add a straight line to the center
slice.path = piePath.CGPath;
[container.layer addSublayer:slice];
}
//7 Adds the container to the main control.
container.userInteractionEnabled = NO;
[self addSubview:container];
//8 Initialize sectors
sectors = [NSMutableArray arrayWithCapacity:numberOfSections];
if (numberOfSections % 2 == 0) {
[self buildSectorsEven];
} else {
[self buildSectorsOdd];
}
}
With the following Code I get always the values of shapeLayer.frame.origin.x = 0 shapeLayer.frame.origin.y =0 shapeLayer.bounds.size.width = 0
shapeLayer.bounds.size.height=0
Did anyone get an Idea? thank you for your answer.
-(void) makeShape {
UIBezierPath * path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(408.000000,230.000000) ];
[path addCurveToPoint:CGPointMake(365.000000,251.000000) controlPoint1:CGPointMake(399.399994,236.000000) controlPoint2:CGPointMake(365.000000,239.000000) ];
[path addCurveToPoint:CGPointMake(309.000000,287.000000) controlPoint1:CGPointMake(353.799988,252.000000) controlPoint2:CGPointMake(309.000000,252.000000) ];
[path addLineToPoint:CGPointMake(236.000000,321.000000) ];
[path addLineToPoint:CGPointMake(207.000000,384.000000) ];
[path addCurveToPoint:CGPointMake(157.000000,416.000000) controlPoint1:CGPointMake(197.000000,388.000000) controlPoint2:CGPointMake(157.000000,390.000000) ];
[path addLineToPoint:CGPointMake(90.000000,489.000000) ];
[path addCurveToPoint:CGPointMake(49.000000,566.000000) controlPoint1:CGPointMake(81.800003,490.000000) controlPoint2:CGPointMake(49.000004,506.000000) ];
[path addCurveToPoint:CGPointMake(19.000000,640.000000) controlPoint1:CGPointMake(35.400002,567.000000) controlPoint2:CGPointMake(18.999998,568.000000) ];
[path addLineToPoint:CGPointMake(365.000000,800.000000) ];
[path addLineToPoint:CGPointMake(415.000000,700.000000) ];
[path closePath];
CAShapeLayer * shapeLayer = [CAShapeLayer layer];
shapeLayer.strokeColor = [[UIColor blackColor] CGColor];
shapeLayer.fillColor = [UIColor grayColor].CGColor;
shapeLayer.lineWidth = 1.5f;
shapeLayer.lineJoin = kCALineJoinRound;
shapeLayer.path = path.CGPath;
[self.view.layer addSublayer:shapeLayer];
float x= shapeLayer.frame.origin.x;
float y = shapeLayer.frame.origin.y;
float w= shapeLayer.bounds.size.width;
float h = shapeLayer.bounds.size.height;
NSLog(#"Point(%f,%f)",x,y);
NSLog(#"Size(%f,%f)",w,h);
}
I figure it out. it's just to set the property bounds with the path.bounds.
but this shrinks the shape and modify it's position when no bounds are set
When I use CAShapeLayer to judge touch is in frame...
I find the Same Question as your question.
So I Use Associate to add a frame When I create CAShapeLayer.
#import <objc/runtime.h>
// To solve CAShapeLayer Frame(0,0,0,0)
#implementation CAShapeLayer (frameCategory)
//http://stackoverflow.com/questions/28447744/frame-origin-and-bounds-size-of-cashapelayer-are-set-to-0
static char kAssociatedObjectKey;
- (void)setFrameValue:(id)frameValue {
objc_setAssociatedObject(self, &kAssociatedObjectKey, frameValue, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (id)frameValue {
return objc_getAssociatedObject(self, &kAssociatedObjectKey);
}
The code below is drawing me a circle, how can I modify the existing code to draw a triangle instead?
_colorDotLayer = [CALayer layer];
CGFloat width = self.bounds.size.width-6;
_colorDotLayer.bounds = CGRectMake(0, 0, width, width);
_colorDotLayer.allowsGroupOpacity = YES;
_colorDotLayer.backgroundColor = self.annotationColor.CGColor;
_colorDotLayer.cornerRadius = width/2;
_colorDotLayer.position = CGPointMake(self.bounds.size.width/2, self.bounds.size.height/2);
While there are shown several Core Graphics solution, I want to add a Core Animation based solution.
- (void)viewDidLoad
{
[super viewDidLoad];
UIBezierPath* trianglePath = [UIBezierPath bezierPath];
[trianglePath moveToPoint:CGPointMake(0, view3.frame.size.height-100)];
[trianglePath addLineToPoint:CGPointMake(view3.frame.size.width/2,100)];
[trianglePath addLineToPoint:CGPointMake(view3.frame.size.width, view2.frame.size.height)];
[trianglePath closePath];
CAShapeLayer *triangleMaskLayer = [CAShapeLayer layer];
[triangleMaskLayer setPath:trianglePath.CGPath];
UIView *view = [[UIView alloc] initWithFrame:CGRectMake(0,0, size.width, size.height)];
view.backgroundColor = [UIColor colorWithWhite:.75 alpha:1];
view.layer.mask = triangleMaskLayer;
[self.view addSubview:view];
}
code based on my blog post.
#implementation TriangleView {
CAShapeLayer *_colorDotLayer;
}
- (void)layoutSubviews {
[super layoutSubviews];
if (_colorDotLayer == nil) {
_colorDotLayer = [CAShapeLayer layer];
_colorDotLayer.fillColor = self.annotationColor.CGColor;
[self.layer addSublayer:_colorDotLayer];
}
CGRect bounds = self.bounds;
CGFloat radius = (bounds.size.width - 6) / 2;
CGFloat a = radius * sqrt((CGFloat)3.0) / 2;
CGFloat b = radius / 2;
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(0, -radius)];
[path addLineToPoint:CGPointMake(a, b)];
[path addLineToPoint:CGPointMake(-a, b)];
[path closePath];
[path applyTransform:CGAffineTransformMakeTranslation(CGRectGetMidX(bounds), CGRectGetMidY(bounds))];
_colorDotLayer.path = path.CGPath;
}
- (void)awakeFromNib {
[super awakeFromNib];
self.annotationColor = [UIColor redColor];
}
#end
Result:
I think is more easy than this solutions:
UIBezierPath *path = [UIBezierPath new];
[path moveToPoint:(CGPoint){20, 0}];
[path addLineToPoint:(CGPoint){40, 40}];
[path addLineToPoint:(CGPoint){0, 40}];
[path addLineToPoint:(CGPoint){20, 0}];
// Create a CAShapeLayer with this triangular path
// Same size as the original imageView
CAShapeLayer *mask = [CAShapeLayer new];
mask.frame = self.viewTriangleCallout.bounds;
mask.path = path.CGPath;
This is my white triangle:
You can change points or rotate if you want:
UIBezierPath *path = [UIBezierPath new];
[path moveToPoint:(CGPoint){0, 0}];
[path addLineToPoint:(CGPoint){40, 0}];
[path addLineToPoint:(CGPoint){20, 20}];
[path addLineToPoint:(CGPoint){0, 0}];
// Create a CAShapeLayer with this triangular path
// Same size as the original imageView
CAShapeLayer *mask = [CAShapeLayer new];
mask.frame = self.viewTriangleCallout.bounds;
mask.path = path.CGPath;
Example code, it is based on this SO Answer which draws stars:
#implementation TriangleView
-(void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
int sides = 3;
double size = 100.0;
CGPoint center = CGPointMake(160.0, 100.0);
double radius = size / 2.0;
double theta = 2.0 * M_PI / sides;
CGContextMoveToPoint(context, center.x, center.y-radius);
for (NSUInteger k=1; k<sides; k++) {
float x = radius * sin(k * theta);
float y = radius * cos(k * theta);
CGContextAddLineToPoint(context, center.x+x, center.y-y);
}
CGContextClosePath(context);
CGContextFillPath(context); // Choose for a filled triangle
// CGContextSetLineWidth(context, 2); // Choose for a unfilled triangle
// CGContextStrokePath(context); // Choose for a unfilled triangle
}
#end
Use UIBezeierPaths
CGFloat radius = 20;
CGMutablePathRef path = CGPathCreateMutable();
CGPathMoveToPoint(path, NULL, (center.x + bottomLeft.x) / 2, (center.y + bottomLeft.y) / 2);
CGPathAddArcToPoint(path, NULL, bottomLeft.x, bottomLeft.y, bottomRight.x, bottomRight.y, radius);
CGPathAddArcToPoint(path, NULL, bottomRight.x, bottomRight.y, center.x, center.y, radius);
CGPathAddArcToPoint(path, NULL, center.x, center.y, bottomLeft.x, bottomLeft.y, radius);
CGPathCloseSubpath(path);
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithCGPath:path];
CGPathRelease(path);
i try to draw a circle for a given percent value (like if i have 25% i just draw a quarter of the circle). At the moment im just able to draw a full circle within my view. Any ideas to my problem?
Code atm:
- (UIBezierPath *)makeCircleAtLocation:(CGPoint)location radius:(CGFloat)radius
{
self.circleCenter = location;
self.circleRadius = radius;
UIBezierPath *path = [UIBezierPath bezierPath];
[path addArcWithCenter:self.circleCenter
radius:self.circleRadius
startAngle:0.0
endAngle:M_PI * 2.0
clockwise:YES];
return path;
}
- (void)drawCircleForLocation{
CGPoint location = CGPointZero;
location.x = self.frame.size.width/2;
location.y = self.frame.size.height/2;
CAShapeLayer *shapeLayer = [CAShapeLayer layer];
shapeLayer.path = [[self makeCircleAtLocation:location radius:9] CGPath];
shapeLayer.strokeColor = [[UIColor whiteColor] CGColor];
shapeLayer.fillColor = nil;
shapeLayer.lineWidth = 1.5;
[self.layer addSublayer:shapeLayer];
}
2 Pi RAD = 360° = full circle.
25% of circle = 2 PI * 25%
- (UIBezierPath *)makeCircleAtLocation:(CGPoint)location radius:(CGFloat)radius percent:(CGFloat)percent
{
self.circleCenter = location; //????
self.circleRadius = radius; //????
UIBezierPath *path = [UIBezierPath bezierPath];
[path addArcWithCenter:location
radius:radius
startAngle:0.0
endAngle:((M_PI * 2.0) * percent)
clockwise:YES];
[path addLineToPoint:location];
[path closePath];
return path;
}
I'm writing an app with a circular progress bar, and have recently changed it to be drawn with a couple of CAShapeLayers (one for the white background, one for the purple progress) so I can animate the purple line, rather than making new UIBeizerPaths in the drawRect of a UIView.
Having made this change, I've had some issues with how it looks now. Below is a screenshot of each way, and a diff (using “Diff” an image using ImageMagick)
My main issue with it is that it looks slightly blurry when using the CAShapeLayers - and I can't for the life of me figure out how to make it look sharper. The other issue is some of the white background is showing through the purple, but I can get around that by changing the width of the white line to be slightly less wide.
The drawRect code as it was originally written is as follows:
- (void)drawRect:(CGRect)rect {
UIBezierPath *backCircle = [UIBezierPath bezierPath];
[backCircle addArcWithCenter:CGPointMake(rect.size.width / 2, rect.size.height / 2)
radius:(rect.size.width/2.0f) - 4.0f
startAngle:(self.endAngle - self.startAngle) * _percent + self.startAngle
endAngle:self.endAngle
clockwise:YES];
backCircle.lineWidth = 5;
[[UIColor whiteColor] setStroke];
[backCircle stroke];
UIBezierPath *bezierPath = [UIBezierPath bezierPath];
[bezierPath addArcWithCenter:CGPointMake(rect.size.width / 2, rect.size.height / 2)
radius:(rect.size.width/2.0f) - 4.0f
startAngle:self.startAngle
endAngle:(self.endAngle - self.startAngle) * _percent + self.startAngle
clockwise:YES];
bezierPath.lineWidth = 5;
[[UIColor purpleColor] setStroke];
[bezierPath stroke];
}
with the init code:
self.startAngle = M_PI * 1.5;
self.endAngle = self.startAngle + (M_PI * 2);
And then with the 2 layers:
- (void)setUpView
{
CGFloat startAngle = M_PI * 1.5;
CGFloat endAngle = startAngle + (M_PI * 2);
UIBezierPath *processPath = [UIBezierPath bezierPath];
[processPath addArcWithCenter:self.boundsCenter
radius:self.radius
startAngle:startAngle
endAngle:endAngle
clockwise:YES];
self.backgroundShapeLayer.path = [processPath CGPath];
self.progressShapeLayer.path = [processPath CGPath];
}
- (CGPoint)boundsCenter
{
return CGPointMake((self.bounds.size.width ) / 2.0, (self.bounds.size.height ) / 2.0);
}
- (CGFloat)radius
{
return (self.bounds.size.width / 2.0) - 4.0f;
}
- (CAShapeLayer *)progressShapeLayer
{
if (_progressShapeLayer == nil) {
_progressShapeLayer = [CAShapeLayer layer];
_progressShapeLayer.fillColor = [[UIColor clearColor] CGColor];
_progressShapeLayer.lineWidth = 5.0;
_progressShapeLayer.strokeStart = 0.0;
_progressShapeLayer.contentsScale = [[UIScreen mainScreen] scale];
[self.layer addSublayer:_progressShapeLayer];
}
return _progressShapeLayer;
}
- (CAShapeLayer *)backgroundShapeLayer
{
if (_backgroundShapeLayer == nil) {
_backgroundShapeLayer = [CAShapeLayer layer];
_backgroundShapeLayer.fillColor = [[UIColor clearColor] CGColor];
_backgroundShapeLayer.strokeColor = [[UIColor whiteColor] CGColor];
_backgroundShapeLayer.lineWidth = 5.0;
_backgroundShapeLayer.strokeStart = 0.0;
_backgroundShapeLayer.contentsScale = [[UIScreen mainScreen] scale];
[self.layer addSublayer:_backgroundShapeLayer];
}
return _backgroundShapeLayer;
}
- (void)setProgress:(CGFloat)progress animated:(BOOL)animated
{
if(isnan(progress) || progress < 0) return;
_progress = progress;
if(!animated) {
[CATransaction setDisableActions:YES];
}
self.progressShapeLayer.strokeColor = [self.progressColour CGColor];
self.progressShapeLayer.strokeEnd = _progress;
}
I really feel like I'm going a bit crazy staring at the zoomed in screen, but it just doesn't look quite right...