Drawing Hexagon like piechart in iOS - ios

I am drawing a hexagon with 3 different colors. I gave a color for each line.
Here is my code;
- (void)drawRect:(CGRect)rect {
self.colors = [[NSMutableArray alloc] initWithObjects:[UIColor yellowColor],[UIColor yellowColor],[UIColor blueColor],[UIColor blueColor],[UIColor greenColor],[UIColor greenColor], nil];
[self addPointsToArray];
CGPoint startPoint = [[self.points objectAtIndex:self.pointCounter] CGPointValue];
CGPoint endPoint = [[self.points objectAtIndex:self.pointCounter+1] CGPointValue];
UIColor *color = [self.colors objectAtIndex:self.pointCounter];
[self drawingEachLineWithDifferentBezier:startPoint endPoint:endPoint color:color];
self.pointCounter++;
}
- (void)addPointsToArray {
self.points = [[NSMutableArray alloc] init];
float polySize = self.frame.size.height/2;
CGFloat hexWidth = self.frame.size.width;
CGFloat hexHeight = self.frame.size.height;
CGPoint center = CGPointMake(hexWidth/2, hexHeight/2);
CGPoint startPoint = CGPointMake(center.x, 0);
for(int i = 3; i >= 1 ; i--) {
CGFloat x = polySize * sinf(i * 2.0 * M_PI / 6);
CGFloat y = polySize * cosf(i * 2.0 * M_PI / 6);
NSLog(#"x = %f, y= %f",x,y);
CGPoint point = CGPointMake(center.x + x, center.y + y);
[self.points addObject:[NSValue valueWithCGPoint:point]];
}
for(int i = 6; i > 3 ; i--) {
CGFloat x = polySize * sinf(i * 2.0 * M_PI / 6);
CGFloat y = polySize * cosf(i * 2.0 * M_PI / 6);
CGPoint point = CGPointMake(center.x + x, center.y + y);
[self.points addObject:[NSValue valueWithCGPoint:point]];
}
[self.points addObject:[NSValue valueWithCGPoint:startPoint]];
}
- (void)drawingEachLineWithDifferentBezier:(CGPoint)startPoint endPoint:(CGPoint)endPoint color:(UIColor *)color {
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:startPoint];
[path addLineToPoint:endPoint];
CAShapeLayer *pathLayer = [CAShapeLayer layer];
pathLayer.frame = self.bounds;
pathLayer.path = path.CGPath;
pathLayer.lineCap = kCALineCapRound;
//pathLayer.lineCap = kCALineCapSquare;
pathLayer.strokeColor = [color CGColor];
pathLayer.fillColor = nil;
pathLayer.lineWidth = 15.0f;
pathLayer.cornerRadius = 2.0f;
pathLayer.lineJoin = kCALineJoinBevel;
//pathLayer.lineDashPattern = #[#15];
[self.layer addSublayer:pathLayer];
CABasicAnimation *pathAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
pathAnimation.duration = 0.3f;
pathAnimation.fromValue = [NSNumber numberWithFloat:0.0f];
pathAnimation.toValue = [NSNumber numberWithFloat:1.0f];
pathAnimation.delegate = self;
[pathLayer addAnimation:pathAnimation forKey:#"strokeEnd"];
}
- (void)animationDidStop:(CAAnimation *)theAnimation finished:(BOOL)flag{
if (self.pointCounter < self.points.count - 1) {
CGPoint startPoint = [[self.points objectAtIndex:self.pointCounter] CGPointValue];
CGPoint endPoint = [[self.points objectAtIndex:self.pointCounter+1] CGPointValue];
UIColor *color = [self.colors objectAtIndex:self.pointCounter];
[self drawingEachLineWithDifferentBezier:startPoint endPoint:endPoint color:color];
self.pointCounter++;
}
}
and I got this view.
My purpose is, i want to be yellow color until red point on 3. line. So i thought if i can find red point coodinate, i can add new point in my points array. Than i can draw yellow line from end of 2. line to red point and blue line from red point to end of 3. line.
Am i on wrong way? If i am not, how can i find red point coordinate or what is your advice?
Thanks for your answer and interest :).

Related

ObjectiveC UIBezierPath path close issue

I try to hexagon and I have some issue in the close path.
Here is my hexagon, and the close path is not smooth.
Here is my drawing code
CAShapeLayer* shapeLayer = [CAShapeLayer layer];
UIBezierPath* path = [UIBezierPath bezierPath];
// [path setLineJoinStyle:kCGLineJoinRound];
// [path setLineJoinStyle:kCGLineJoinBevel];
[path setLineJoinStyle:kCGLineJoinMiter];
// CGFloat dashes[] = {6, 2};
// [path setLineDash:dashes count:2 phase:0];
// [path stroke];
CGFloat radians = 100.0;
NSInteger num = 6;
CGFloat interval = 2*M_PI/num;
NSInteger initX = radians*cosf(interval);
NSInteger initY = radians*sinf(interval);
[path moveToPoint:CGPointMake(location.x - semiWidth + initX, location.y - semiHeight + initY)];
for(int i=1; i<=num; i++){
CGFloat x = radians*cosf(i*interval);
CGFloat y = radians*sinf(i*interval);
[path addLineToPoint:CGPointMake(location.x - semiWidth + x, location.y - semiHeight + y)];
}
[path closePath];
shapeLayer.path = [path CGPath];
shapeLayer.strokeColor = [[UIColor yellowColor] CGColor];
shapeLayer.fillColor = [[UIColor brownColor] CGColor];
shapeLayer.lineWidth = 4.0f;
Also I try to use different options as following with no luck
[path setLineJoinStyle:kCGLineJoinRound];
[path setLineJoinStyle:kCGLineJoinBevel];
[path setLineJoinStyle:kCGLineJoinMiter];
The problem is that you're not making your first point (the point you move-to) the same way you're making your other points (the points you line-to).
NSInteger initX = radians*cosf(interval);
NSInteger initY = radians*sinf(interval);
[path moveToPoint:CGPointMake(
location.x - semiWidth + initX, location.y - semiHeight + initY)];
Instead, make the first point completely parallel with the others:
CGFloat x = radians*cosf(0*interval);
CGFloat y = radians*sinf(0*interval);
[path moveToPoint:CGPointMake(
location.x - semiWidth + x, location.y - semiHeight + y)];
That's exactly the same as what you'll do later with i*interval, and to emphasize that parallelism, I've written 0 as 0*interval. Here's what I ended up with:

CAShapeLayer Random Fill Color

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

iOS - Pie progress bar with rounded corners

I need to implement a progress bar according to this design :
As you can see, there is a corner radius to the bar itself.
This is how it looks now with my current code :
So, how to do that?
This is my current code:
- (void)animateProgressBarToPercent:(float)percent
{
if (percent > 1.0f) return;
int radius = 42.7f;
int strokeWidth = 7.f;
CGColorRef color = [UIColor someColor].CGColor;
int timeInSeconds = percent * 5;
CGFloat startAngle = 0;
CGFloat endAngle = percent;
CAShapeLayer *circle = [CAShapeLayer layer];
circle.path = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(0, 0, 2.0*radius, 2.0*radius) cornerRadius:radius].CGPath;
circle.position = CGPointMake(self.center.x - radius, self.center.y + strokeWidth);
circle.fillColor = [UIColor clearColor].CGColor;
circle.strokeColor = color;
circle.lineWidth = strokeWidth;
circle.strokeEnd = endAngle;
[self.layer addSublayer:circle];
CABasicAnimation *drawAnimation = [CABasicAnimation animationWithKeyPath:#"strokeEnd"];
drawAnimation.duration = timeInSeconds;
drawAnimation.repeatCount = 1.0;
drawAnimation.removedOnCompletion = NO;
drawAnimation.fromValue = [NSNumber numberWithFloat:startAngle];
drawAnimation.toValue = [NSNumber numberWithFloat:endAngle];
drawAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[circle addAnimation:drawAnimation forKey:#"drawCircleAnimation"];
}
circle.lineCap = kCALineCapRound
Just set this property of your CAShapeLayer

Cutting an image to a CALayers content during an animation

I have created a subclass of CALayer to draw a slice of a pie chart with a custom color (color), a startAngle (actualStartAngle), an endAngle (actualEndAngle) and an image (image)[This image should always be in the middle of the slice] as animating this is not possible with a normal CAShapeLayer. The layer is currently drawn like this:
- (void)drawInContext:(CGContextRef)ctx{
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, self.center.x, self.center.y);
CGContextAddArc(ctx, self.center.x, self.center.y, self.radius, self.actualEndAngle, self.actualStartAngle, YES);
CGContextClosePath(ctx);
CGContextSetFillColorWithColor(ctx, self.color);
CGContextSetLineWidth(ctx, 0);
CGContextDrawPath(ctx, kCGPathFillStroke);
if (self.image) {
CGContextDrawImage(ctx, [self getFrameForImgLayerWithStartAngle:self.actualStartAngle andEndAngle:self.actualEndAngle], self.image);
}
}
These individual slices are animated by:
[CATransaction begin];
CABasicAnimation *colorAnimation = [CABasicAnimation animationWithKeyPath:#"color"];
colorAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
colorAnimation.duration = duration;
colorAnimation.fromValue = (id)self.color;
colorAnimation.toValue = (id)colorTU.CGColor;
CABasicAnimation *startAngleAnimation = [CABasicAnimation animationWithKeyPath:#"actualStartAngle"];
startAngleAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
startAngleAnimation.duration = duration;
startAngleAnimation.fromValue = [self valueForKey:#"actualStartAngle"];
startAngleAnimation.toValue = [[NSNumber alloc] initWithFloat:newStartAngle];
CABasicAnimation *endAngleAnimation = [CABasicAnimation animationWithKeyPath:#"actualEndAngle"];
endAngleAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
endAngleAnimation.duration = duration;
endAngleAnimation.fromValue = [self valueForKey:#"actualEndAngle"];
endAngleAnimation.toValue = [[NSNumber alloc] initWithFloat:newEndAngle];
if (completionBlock) {
[CATransaction setCompletionBlock:completionBlock];
}
if (colorTU && colorTU.CGColor != self.color) {
[self addAnimation:colorAnimation forKey:#"color"];
}
if (newStartAngle != self.actualStartAngle) {
[self addAnimation:startAngleAnimation forKey:#"actualStartAngle"];
}
if (newEndAngle != self.actualEndAngle) {
[self addAnimation:endAngleAnimation forKey:#"actualEndAngle"];
}
[CATransaction commit];
The problem is that I would like to have the image only take up the space that the individual layer takes up (resp. the shape in it). This is solved in general by this method:
- (BOOL)imageFitsIntoSliceWithStartAngle:(float)sA andEndAngle:(float)eA{
BOOL fits = YES;
CGRect rectToUse = [self getFrameForImgLayerWithStartAngle:sA andEndAngle:eA];
UIBezierPath *path = [UIBezierPath bezierPath];
[path moveToPoint:self.center];
[path addArcWithCenter:self.center radius:self.deg startAngle:sA endAngle:eA clockwise:YES];
for (int w = 0; w <= 1; w++) {
if (fits) {
for (int h = 0; h <= 1; h++) {
if (![path containsPoint:CGPointMake(rectToUse.origin.x + rectToUse.size.width * w, rectToUse.origin.y + rectToUse.size.height * h)]) {
fits = NO;
break;
}
}
}
}
return fits;
}
The problem now is that it may fit in the end and not at the beginning of the animation, so I would have to cut the image to be as big as the slice when this happens.
I have tried the following:
set a mask for the slice instead of drawing it directly -> This is firstly bad coding and I have found it to be very energy inefficient, also it created an error (expecting model layer not copy) for me.
Any ideas on how to solve this issue? (not what I tried but this problem in general, I don't think you should do something like what I tried above)
Thanks
Don't know if it'll help, but here the drawing code for a pie chart part :
- (void)drawChartWithFrame:(CGRect)frame startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle fillColor:(UIColor *)fillColor strokeColor:(UIColor *)strokeColor
{
//// Oval Drawing
CGRect ovalRect = CGRectMake(CGRectGetMinX(frame),CGRectGetMinY(frame), CGRectGetWidth(frame), CGRectGetHeight(frame));
UIBezierPath* ovalPath = UIBezierPath.bezierPath;
[ovalPath addArcWithCenter: CGPointMake(CGRectGetMidX(ovalRect), CGRectGetMidY(ovalRect)) radius: CGRectGetWidth(ovalRect) / 2 startAngle: -(startAngle - 25) * M_PI/180 endAngle: -(endAngle - 93) * M_PI/180 clockwise: YES];
[ovalPath addLineToPoint: CGPointMake(CGRectGetMidX(ovalRect), CGRectGetMidY(ovalRect))];
[ovalPath closePath];ansform ovalTransform = CGAffineTransformMakeTranslation(CGRectGetMidX(ovalRect), CGRectGetMidY(ovalRect));
ovalTransform = CGAffineTransformScale(ovalTransform, 1, CGRectGetHeight(ovalRect) / CGRectGetWidth(ovalRect));
[ovalPath applyTransform: ovalTransform];
[fillColor setFill];
[ovalPath fill];
[strokeColor setStroke];
ovalPath.lineWidth = 1;
[ovalPath stroke];
}
It's much lighter to animate that a real image.

iPad layer shadow effect for UIView

I am trying to display a UIView similar to this image url
Click here to view uiview image
This is a UIView containing UIimageview and label.
Is there a way to set layer effect similar to this?
Thanks in advance
Joe.
#import <QuartzCore/QuartzCore.h>
you can use
imageView.layer.shadowRadius = 2.0;
imageView.layer.shadowOpacity = 0.7;
imageView.layer.shadowColor = [UIColor blackColor].CGColor;
imageView.layer.shadowPath = [self createArcShadowPathForRect:imageView.frame].CGPath
#define archeight 25
-(UIBezierPath *)createArcShadowPathForRect:(CGRect)rect {
CGFloat h_padding = (self.imageView.frame.size.width - rect.size.width) /2;
CGFloat v_padding = (self.imageView.frame.size.height - rect.size.height) /2-10;
CGPoint startPoint = CGPointMake(0 + h_padding, rect.size.height + v_padding) ;
CGPoint pointTwo = CGPointMake(0 + h_padding, rect.size.height + archeight + v_padding) ;
CGPoint controlCenterPoint = CGPointMake(rect.size.width/2 + h_padding, rect.size.height + v_padding+ 10);
CGPoint leftDownPoint = CGPointMake(rect.size.width + h_padding, rect.size.height + archeight + v_padding) ;
CGPoint leftUpPoint = CGPointMake(rect.size.width + h_padding, rect.size.height + v_padding) ;
UIBezierPath *path = nil;
if(!path) {
path = [[UIBezierPath bezierPath] retain];
[path moveToPoint:startPoint];
[path moveToPoint:pointTwo];
[path addQuadCurveToPoint:leftDownPoint controlPoint:controlCenterPoint];
[path addLineToPoint:leftUpPoint];
[path addLineToPoint:startPoint];
[path closePath];
}
return path;
}

Resources