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];
}
}
Related
I need help in adding arrow head to UIBezierPath.
I have created multiple lines by using UIBezierPath, which are in different directions.
Now, I want to create arrow head at the end point of the line.
I tried to add UIImage to UILable using NSAttributedString to UILable with NSTextAttachment.
But, here problem is arrow head is always down. And I want to align arrow along the line.
Here is my code:
UIBezierPath *ShotPath = [UIBezierPath bezierPath];
NSValue *PointValue = [[[PathArray objectAtIndex:i] valueForKey:#"ShotPath"] objectAtIndex:k];
CGPoint FirstPoint; = [PointValue CGPointValue];
[ShotPath moveToPoint:FirstPoint];
CAShapeLayer *line = [CAShapeLayer layer];
line.lineWidth = 4.0;
line.path=ShotPath.CGPath;
line.fillColor = [UIColor clearColor].CGColor;
line.strokeColor = [UIColor colorWithRed:231/255.0 green:83/255.0 blue:73/255.0 alpha:1].CGColor;
[[BaseView layer] addSublayer:line];
NSValue *PointValue = [[[PathArray objectAtIndex:i] valueForKey:#"ShotPath"] objectAtIndex:[[[PathArray objectAtIndex:i] valueForKey:#"ShotPath"] count]-1];
CGPoint OtherPoint = [PointValue CGPointValue];
UILabel *lbl = [[UILabel alloc]initWithFrame:CGRectMake(OtherPoint.x-6, OtherPoint.y-8, 12, 12)];
lbl.backgroundColor =[UIColor clearColor];
NSTextAttachment *attachment = [[NSTextAttachment alloc] init];
attachment.image = [UIImage imageNamed:#"DropDown_Icon"];
NSAttributedString *attachmentString = [NSAttributedString attributedStringWithAttachment:attachment];
lbl.attributedText = attachmentString;
[lbl setTextColor:[UIColor blackColor]];
lbl.font =[UIFont boldSystemFontOfSize:22.0];
[BaseView addSubview:lbl];
Here is my current output :
By the reference of this answer:
append arrowhead to UIBezierPath
Solved my query:
Answer:
- (void)viewDidLoad {
[super viewDidLoad];
UIBezierPath *path = [UIBezierPath bezierPath];
CGPoint point1 = CGPointMake(100, 100);
CGPoint point2 = CGPointMake(300, 300);
[path moveToPoint:point1];
[path addQuadCurveToPoint:point2 controlPoint:point1];
CAShapeLayer *shape = [CAShapeLayer layer];
shape.path = path.CGPath;
shape.lineWidth = 10;
shape.strokeColor = [UIColor blueColor].CGColor;
shape.fillColor = [UIColor clearColor].CGColor;
shape.frame = self.view.bounds;
[self.view.layer addSublayer:shape];
CGFloat angle = atan2f(point2.y - point1.y, point2.x - point1.x);
CGFloat distance = 15.0;
path = [UIBezierPath bezierPath];
[path moveToPoint:point2];
[path addLineToPoint:[self calculatePointFromPoint:point2 angle:angle + M_PI_2 distance:distance]]; // to the right
[path addLineToPoint:[self calculatePointFromPoint:point2 angle:angle distance:distance]]; // straight ahead
[path addLineToPoint:[self calculatePointFromPoint:point2 angle:angle - M_PI_2 distance:distance]]; // to the left
[path closePath];
shape = [CAShapeLayer layer];
shape.path = path.CGPath;
shape.lineWidth = 2;
shape.strokeColor = [UIColor redColor].CGColor;
shape.fillColor = [UIColor redColor].CGColor;
shape.frame = self.view.bounds;
[self.view.layer addSublayer:shape];
}
- (CGPoint)calculatePointFromPoint:(CGPoint)point angle:(CGFloat)angle distance:(CGFloat)distance {
return CGPointMake(point.x + cosf(angle) * distance, point.y + sinf(angle) * distance);
}
i want to draw an wifi signal pulse and want to animate with that in launch screen. i tried with this code. the signal is showing in the down words but i need to show in up words. Starting from a point ti increase [like our phone wifi signal]
- (void)viewDidLoad
{
[super viewDidLoad];
[self addWavesToView];
[self addAnimationsToLayers];
}
- (void)addWavesToView
{
CGRect rect = CGRectMake(0.0f, 0.0f, 300.0f, 300.0f);
UIBezierPath *circlePath = [UIBezierPath
bezierPathWithOvalInRect:rect];
for (NSInteger i = 0; i < 10; ++i) {
CAShapeLayer *waveLayer = [CAShapeLayer layer];
waveLayer.bounds = rect;
waveLayer.position = CGPointMake(100, 100);
waveLayer.strokeColor = [[UIColor lightGrayColor] CGColor];
waveLayer.fillColor = [[UIColor clearColor] CGColor];
waveLayer.lineWidth = 5.0f;
waveLayer.path = circlePath.CGPath;
waveLayer.transform = CATransform3DMakeTranslation(0.0f, i*25, 0.0f);
waveLayer.strokeStart = 0.25- ((i+1) * 0.01f);
waveLayer.strokeEnd = 0.25+((i+1) * 0.01f);
[self.view.layer addSublayer:waveLayer];
}
}
- (void)addAnimationsToLayers
{
NSInteger timeOffset = 10;
for (CALayer *layer in self.view.layer.sublayers) {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:#"strokeColor"];
animation.duration = 10.0f;
// Stagger the animations so they don't begin until the
// desired time in each
animation.timeOffset = timeOffset--;
animation.toValue = (id)[[UIColor lightGrayColor] CGColor];
animation.fromValue = (id)[[UIColor darkGrayColor] CGColor];
// Repeat forever
animation.repeatCount = HUGE_VALF;
// Run it at 10x. You can adjust this to taste.
animation.speed = 10;
// Add to the layer to start the animation
[layer addAnimation:animation forKey:#"strokeColor"];
}
"show in up words", it'll take more time so we can rotate base view to 180 degree by this code inside of viewDidLoad() Function
CGFloat degreesOfRotation = 180.0;
self.view.transform = CGAffineTransformRotate(self.view.transform,
degreesOfRotation * M_PI/180.0);
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 :).
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;
}
I'm working through a challenge in Big Nerd Ranch's iOS Programming guide in a chapter on UIView subclassing.
I have some code below that draws concentric circles in random colors. And, upon a shake, it should change them all to red. It doesn't because after red is set, drawRect is called again and the random color loop is redone.
Would the correct way to implement this be to move the random color loop somewhere out of drawRect? Do I set a semaphore (messy) to make sure the loop only runs once?
Thanks for your advice.
- (void)setCircleColor:(UIColor *)clr
{
circleColor = clr;
[self setNeedsDisplay];
}
- (BOOL) canBecomeFirstResponder
{
return YES;
}
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event
{
if (motion == UIEventSubtypeMotionShake) {
[self setCircleColor:[UIColor redColor]];
}
}
-(void)drawRect:(CGRect)dirtyRect
{
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGRect bounds = [self bounds];
// Figure out the center of the bounds rectangle
CGPoint center;
center.x = bounds.origin.x + bounds.size.width / 2.0;
center.y = bounds.origin.y + bounds.size.height / 2.0;
float maxRadius = hypot(bounds.size.width, bounds.size.height) / 2.0;
CGContextSetLineWidth(ctx, 10);
// Set up an array of colors
UIColor *red = [UIColor redColor];
UIColor *green = [UIColor greenColor];
UIColor *yellow = [UIColor yellowColor];
NSArray *colors = [[NSArray alloc]initWithObjects:red, green, yellow, nil];
// Draw concentric circles from the outside in
for (float currentRadius = maxRadius; currentRadius > 0; currentRadius -= 20) {
CGContextAddArc(ctx, center.x, center.y, currentRadius, 0.0, M_PI * 2, YES);
// Random index for colors array
NSUInteger randomIndex = arc4random() % [colors count];
[self setCircleColor:[colors objectAtIndex:randomIndex]];
[[self circleColor] setStroke];
// Perform drawing; remove path
CGContextStrokePath(ctx);
}
NSString *text = #"You are getting sleepy.";
UIFont *font = [UIFont boldSystemFontOfSize:28];
CGRect textRect;
textRect.size = [text sizeWithFont:font];
// Let's put that string in the center of the view
textRect.origin.x = center.x - textRect.size.width / 2.0;
textRect.origin.y = center.y - textRect.size.height / 2.0;
[[UIColor blackColor] setFill];
// Draw a shadow
CGSize offset = CGSizeMake(4, 3);
CGColorRef color = [[UIColor darkGrayColor] CGColor];
CGContextSetShadowWithColor(ctx, offset, 2.0, color);
[text drawInRect:textRect withFont:font];
}
-(id)initWithFrame:(CGRect)frame
{
self = [super initWithFrame:frame];
if (self) {
[self setBackgroundColor:[UIColor clearColor]];
[self setCircleColor:[UIColor lightGrayColor]];
}
return self;
}
#end
Make a BOOL isSHake and cancel the random colors after you shake the device, the circles will remain red and you can reset that upon another action.